Merge the last PGO-green inbound changeset to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 01 Nov 2012 21:26:34 -0400
changeset 112085 556b9cfb269f931fa1f487cbd4053e2ac157894a
parent 112040 c5fbfc1b1a94b19ce586976aa950aaf949562302 (current diff)
parent 112084 8c792aed3baac9247a97061dffeae0f3ee3d1f3a (diff)
child 112086 00c925c90f8686d9603908239e7b193bf7b5e269
child 112137 e17c3f1ca7e38478faac5ce5d75856d18c422cfe
push id23790
push userryanvm@gmail.com
push dateFri, 02 Nov 2012 01:26:40 +0000
treeherdermozilla-central@556b9cfb269f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone19.0a1
first release with
nightly linux32
556b9cfb269f / 19.0a1 / 20121102030726 / files
nightly linux64
556b9cfb269f / 19.0a1 / 20121102030726 / files
nightly mac
556b9cfb269f / 19.0a1 / 20121102030726 / files
nightly win32
556b9cfb269f / 19.0a1 / 20121102030726 / files
nightly win64
556b9cfb269f / 19.0a1 / 20121102030726 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge the last PGO-green inbound changeset to m-c.
netwerk/protocol/http/nsHttpHandler.cpp
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -3079,17 +3079,17 @@ Accessible::GetAttrValue(nsIAtom *aPrope
     *aValue = value;
 
   return NS_OK;
 }
 
 uint32_t
 Accessible::GetActionRule()
 {
-  if (InteractiveState() & states::UNAVAILABLE)
+  if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE))
     return eNoAction;
 
   // Check if it's simple xlink.
   if (nsCoreUtils::IsXLink(mContent))
     return eJumpAction;
 
   // Return "click" action on elements that have an attached popup menu.
   if (mContent->IsXUL())
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -869,18 +869,23 @@
             newBrowser.setAttribute("type", "content-primary");
             newBrowser.docShellIsActive =
               (window.windowState != window.STATE_MINIMIZED);
             this.mCurrentBrowser = newBrowser;
             this.mCurrentTab = this.selectedTab;
             this.showTab(this.mCurrentTab);
 
             var backForwardContainer = document.getElementById("unified-back-forward-button");
-            if (backForwardContainer)
+            if (backForwardContainer) {
               backForwardContainer.setAttribute("switchingtabs", "true");
+              window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
+                window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
+                backForwardContainer.removeAttribute("switchingtabs");
+              });
+            }
 
             if (updatePageReport)
               this.mCurrentBrowser.updatePageReport();
 
             // Update the URL bar.
             var loc = this.mCurrentBrowser.currentURI;
 
 #ifdef MOZ_E10S_COMPAT
@@ -941,19 +946,16 @@
               this.mIsBusy = false;
               this._callProgressListeners(null, "onStateChange",
                                           [webProgress, null,
                                            nsIWebProgressListener.STATE_STOP |
                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
                                           true, false);
             }
 
-            if (backForwardContainer)
-              backForwardContainer.removeAttribute("switchingtabs");
-
             if (this.mCurrentTab.selected)
               this._setCloseKeyState(!this.mCurrentTab.pinned);
 
             // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
             // that might rely upon the other changes suppressed.
             // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
             if (!this._previewMode) {
               // We've selected the new tab, so go ahead and notify listeners.
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -15,20 +15,20 @@ const CHROME_DEBUGGER_PROFILE_NAME = "_c
 const TAB_SWITCH_NOTIFICATION = "debugger-tab-switch";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
-  "Services", "resource:///modules/Services.jsm");
+  "Services", "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
-  "FileUtils", "resource:///modules/FileUtils.jsm");
+  "FileUtils", "resource://gre/modules/FileUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["DebuggerUI"];
 
 /**
  * Provides a simple mechanism of managing debugger instances.
  *
  * @param nsIDOMWindow aWindow
  *        The chrome window for which the DebuggerUI instance is created.
--- a/build/virtualenv/packages.txt
+++ b/build/virtualenv/packages.txt
@@ -1,18 +1,21 @@
 simplejson.pth:python/simplejson-2.1.1
 manifestdestiny.pth:testing/mozbase/manifestdestiny
 mozcrash.pth:testing/mozbase/mozcrash
 mozdevice.pth:testing/mozbase/mozdevice
+mozfile.pth:testing/mozbase/mozfile
+mozhttpd.pth:testing/mozbase/mozhttpd
 mozinfo.pth:testing/mozbase/mozinfo
 mozinstall.pth:testing/mozbase/mozinstall
 mozlog.pth:testing/mozbase/mozlog
 mozprocess.pth:testing/mozbase/mozprocess
 mozprofile.pth:testing/mozbase/mozprofile
 mozrunner.pth:testing/mozbase/mozrunner
+marionette.pth:testing/marionette/client
 blessings.pth:python/blessings
 mozbuild.pth:python/mozbuild
 pymake.pth:build/pymake
 optional:setup.py:python/psutil:build_ext:--inplace
 optional:psutil.pth:python/psutil
 which.pth:python/which
 mozilla.pth:build
 mozilla.pth:config
--- a/config/JarMaker.py
+++ b/config/JarMaker.py
@@ -59,29 +59,33 @@ def getModTime(aPath):
 
 class JarMaker(object):
   '''JarMaker reads jar.mn files and process those into jar files or
   flat directories, along with chrome.manifest files.
   '''
 
   ignore = re.compile('\s*(\#.*)?$')
   jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/]+).jar\:)|(?:\s*(\#.*)?)\s*$')
+  relsrcline = re.compile('relativesrcdir\s+(?P<relativesrcdir>.+?):')
   regline = re.compile('\%\s+(.*)$')
   entryre = '(?P<optPreprocess>\*)?(?P<optOverwrite>\+?)\s+'
   entryline = re.compile(entryre + '(?P<output>[\w\d.\-\_\\\/\+\@]+)\s*(\((?P<locale>\%?)(?P<source>[\w\d.\-\_\\\/\@]+)\))?\s*$')
 
   def __init__(self, outputFormat = 'flat', useJarfileManifest = True,
                useChromeManifest = False):
     self.outputFormat = outputFormat
     self.useJarfileManifest = useJarfileManifest
     self.useChromeManifest = useChromeManifest
     self.pp = Preprocessor()
     self.topsourcedir = None
     self.sourcedirs = []
     self.localedirs = None
+    self.l10nbase = None
+    self.l10nmerge = None
+    self.relativesrcdir = None
 
   def getCommandLineParser(self):
     '''Get a optparse.OptionParser for jarmaker.
 
     This OptionParser has the options for jarmaker as well as
     the options for the inner PreProcessor.
     '''
     # HACK, we need to unescape the string variables we get,
@@ -100,16 +104,22 @@ class JarMaker(object):
                  dest="bothManifests",
                  help="create chrome.manifest and jarfile.manifest")
     p.add_option('-s', type="string", action="append", default=[],
                  help="source directory")
     p.add_option('-t', type="string",
                  help="top source directory")
     p.add_option('-c', '--l10n-src', type="string", action="append",
                  help="localization directory")
+    p.add_option('--l10n-base', type="string", action="store",
+                 help="base directory to be used for localization (requires relativesrcdir)")
+    p.add_option('--locale-mergedir', type="string", action="store",
+                 help="base directory to be used for l10n-merge (requires l10n-base and relativesrcdir)")
+    p.add_option('--relativesrcdir', type="string",
+                 help="relativesrcdir to be used for localization")
     p.add_option('-j', type="string",
                  help="jarfile directory")
     return p
 
   def processIncludes(self, includes):
     '''Process given includes with the inner PreProcessor.
 
     Only use this for #defines, the includes shouldn't generate
@@ -165,29 +175,31 @@ class JarMaker(object):
             continue
           myregister[l] = None
         mf.seek(0)
       for k in myregister.iterkeys():
         mf.write(k + os.linesep)
       mf.close()
     finally:
       lock = None
-  
+
   def makeJar(self, infile, jardir):
     '''makeJar is the main entry point to JarMaker.
 
     It takes the input file, the output directory, the source dirs and the
     top source dir as argument, and optionally the l10n dirs.
     '''
     # making paths absolute, guess srcdir if file and add to sourcedirs
     _normpath = lambda p: os.path.normpath(os.path.abspath(p))
     self.topsourcedir = _normpath(self.topsourcedir)
     self.sourcedirs = [_normpath(p) for p in self.sourcedirs]
     if self.localedirs:
       self.localedirs = [_normpath(p) for p in self.localedirs]
+    elif self.relativesrcdir:
+      self.localedirs = self.generateLocaleDirs(self.relativesrcdir)
     if isinstance(infile, basestring):
       logging.info("processing " + infile)
       self.sourcedirs.append(_normpath(os.path.dirname(infile)))
     pp = self.pp.clone()
     pp.out = StringIO()
     pp.do_include(infile)
     lines = pushback_iter(pp.out.getvalue().splitlines())
     try:
@@ -200,16 +212,33 @@ class JarMaker(object):
           # comment
           continue
         self.processJarSection(m.group('jarfile'), lines, jardir)
     except StopIteration:
       # we read the file
       pass
     return
 
+  def generateLocaleDirs(self, relativesrcdir):
+    if os.path.basename(relativesrcdir) == 'locales':
+      # strip locales
+      l10nrelsrcdir = os.path.dirname(relativesrcdir)
+    else:
+      l10nrelsrcdir = relativesrcdir
+    locdirs = []
+    # generate locales dirs, merge, l10nbase, en-US
+    if self.l10nmerge:
+      locdirs.append(os.path.join(self.l10nmerge, l10nrelsrcdir))
+    if self.l10nbase:
+      locdirs.append(os.path.join(self.l10nbase, l10nrelsrcdir))
+    if self.l10nmerge or not self.l10nbase:
+      # add en-US if we merge, or if it's not l10n
+      locdirs.append(os.path.join(self.topsourcedir, relativesrcdir, 'en-US'))
+    return locdirs
+
   def processJarSection(self, jarfile, lines, jardir):
     '''Internal method called by makeJar to actually process a section
     of a jar.mn file.
 
     jarfile is the basename of the jarfile or the directory name for 
     flat output, lines is a pushback_iterator of the lines of jar.mn,
     the remaining options are carried over from makeJar.
     '''
@@ -249,16 +278,21 @@ class JarMaker(object):
           # we're done with this jar.mn, and this jar section
           self.finalizeJar(jarfile, chromebasepath, register)
           if jf is not None:
             jf.close()
           # reraise the StopIteration for makeJar
           raise
         if self.ignore.match(l):
           continue
+        m = self.relsrcline.match(l)
+        if m:
+          relativesrcdir = m.group('relativesrcdir')
+          self.localedirs = self.generateLocaleDirs(relativesrcdir)
+          continue
         m = self.regline.match(l)
         if  m:
           rline = m.group(1)
           register[rline] = 1
           continue
         m = self.entryline.match(l)
         if not m:
           # neither an entry line nor chrome reg, this jar section is done
@@ -318,17 +352,17 @@ class JarMaker(object):
           outHelper.symlink(realsrc, out)
           return
         outf = outHelper.getOutput(out)
         # open in binary mode, this can be images etc
         inf = open(realsrc, 'rb')
         outf.write(inf.read())
         outf.close()
         inf.close()
-    
+
 
   class OutputHelper_jar(object):
     '''Provide getDestModTime and getOutput for a given jarfile.
     '''
     def __init__(self, jarfile):
       self.jarfile = jarfile
     def getDestModTime(self, aPath):
       try :
@@ -397,16 +431,26 @@ def main():
   jm.sourcedirs = options.s
   jm.topsourcedir = options.t
   if options.e:
     jm.useChromeManifest = True
     jm.useJarfileManifest = False
   if options.bothManifests:
     jm.useChromeManifest = True
     jm.useJarfileManifest = True
+  if options.l10n_base:
+    if not options.relativesrcdir:
+      p.error('relativesrcdir required when using l10n-base')
+    if options.l10n_src:
+      p.error('both l10n-src and l10n-base are not supported')
+    jm.l10nbase = options.l10n_base
+    jm.relativesrcdir = options.relativesrcdir
+    jm.l10nmerge = options.locale_mergedir
+  elif options.locale_mergedir:
+    p.error('l10n-base required when using locale-mergedir')
   jm.localedirs = options.l10n_src
   noise = logging.INFO
   if options.verbose is not None:
     noise = (options.verbose and logging.DEBUG) or logging.WARN
   if sys.version_info[:2] > (2,3):
     logging.basicConfig(format = "%(message)s")
   else:
     logging.basicConfig()
--- a/config/config.mk
+++ b/config/config.mk
@@ -698,27 +698,31 @@ else
 endif
 
 EXPAND_LOCALE_SRCDIR = $(if $(filter en-US,$(AB_CD)),$(topsrcdir)/$(1)/en-US,$(call core_realpath,$(L10NBASEDIR))/$(AB_CD)/$(subst /locales,,$(1)))
 
 ifdef relativesrcdir
 LOCALE_SRCDIR = $(call EXPAND_LOCALE_SRCDIR,$(relativesrcdir))
 endif
 
-ifdef LOCALE_SRCDIR
-# if LOCALE_MERGEDIR is set, use mergedir first, then the localization,
-# and finally en-US
+ifdef relativesrcdir
+MAKE_JARS_FLAGS += --relativesrcdir=$(relativesrcdir)
+ifneq (en-US,$(AB_CD))
 ifdef LOCALE_MERGEDIR
-MAKE_JARS_FLAGS += -c $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir))
+MAKE_JARS_FLAGS += --locale-mergedir=$(LOCALE_MERGEDIR)
 endif
+ifdef IS_LANGUAGE_REPACK
+MAKE_JARS_FLAGS += --l10n-base=$(L10NBASEDIR)
+endif
+else
 MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR)
-ifdef LOCALE_MERGEDIR
-MAKE_JARS_FLAGS += -c $(topsrcdir)/$(relativesrcdir)/en-US
-endif
-endif
+endif # en-US
+else
+MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR)
+endif # ! relativesrcdir
 
 ifdef LOCALE_MERGEDIR
 MERGE_FILE = $(firstword \
   $(wildcard $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir))/$(1)) \
   $(wildcard $(LOCALE_SRCDIR)/$(1)) \
   $(srcdir)/en-US/$(1) )
 else
 MERGE_FILE = $(LOCALE_SRCDIR)/$(1)
--- a/config/tests/unit-JarMaker.py
+++ b/config/tests/unit-JarMaker.py
@@ -1,14 +1,15 @@
 import unittest
 
 import os, sys, os.path, time, inspect
 from filecmp import dircmp
 from tempfile import mkdtemp
 from shutil import rmtree, copy2
+from StringIO import StringIO
 from zipfile import ZipFile
 import mozunit
 from JarMaker import JarMaker
 
 if sys.platform == "win32":
     import ctypes
     from ctypes import POINTER, WinError
     DWORD = ctypes.c_ulong
@@ -235,10 +236,69 @@ class TestJarMaker(unittest.TestCase):
         jm.makeJar(os.path.join(self.srcdir,'jar.mn'), jardir)
         # All we do is check that srcdir/bar points to builddir/chrome/test/dir/foo
         srcbar = os.path.join(self.srcdir, 'bar')
         destfoo = os.path.join(self.builddir, 'chrome', 'test', 'dir', 'foo')
         self.assertTrue(is_symlink_to(destfoo, srcbar),
                         "%s is not a symlink to %s" % (destfoo, srcbar))
 
 
+class Test_relativesrcdir(unittest.TestCase):
+    def setUp(self):
+        self.jm = JarMaker()
+        self.jm.topsourcedir = '/TOPSOURCEDIR'
+        self.jm.relativesrcdir = 'browser/locales'
+        self.fake_empty_file = StringIO()
+        self.fake_empty_file.name = 'fake_empty_file'
+    def tearDown(self):
+        del self.jm
+        del self.fake_empty_file
+    def test_en_US(self):
+        jm = self.jm
+        jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED')
+        self.assertEquals(jm.localedirs,
+                          [
+                            os.path.join(os.path.abspath('/TOPSOURCEDIR'),
+                                         'browser/locales', 'en-US')
+                            ])
+    def test_l10n_no_merge(self):
+        jm = self.jm
+        jm.l10nbase = '/L10N_BASE'
+        jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED')
+        self.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'browser')])
+    def test_l10n_merge(self):
+        jm = self.jm
+        jm.l10nbase = '/L10N_BASE'
+        jm.l10nmerge = '/L10N_MERGE'
+        jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED')
+        self.assertEquals(jm.localedirs,
+                          [os.path.join('/L10N_MERGE', 'browser'),
+                           os.path.join('/L10N_BASE', 'browser'),
+                           os.path.join(os.path.abspath('/TOPSOURCEDIR'),
+                                        'browser/locales', 'en-US')
+                           ])
+    def test_override(self):
+        jm = self.jm
+        jm.outputFormat = 'flat'  # doesn't touch chrome dir without files
+        jarcontents = StringIO('''en-US.jar:
+relativesrcdir dom/locales:
+''')
+        jarcontents.name = 'override.mn'
+        jm.makeJar(jarcontents, '/NO_OUTPUT_REQUIRED')
+        self.assertEquals(jm.localedirs,
+                          [
+                            os.path.join(os.path.abspath('/TOPSOURCEDIR'),
+                                         'dom/locales', 'en-US')
+                            ])
+    def test_override_l10n(self):
+        jm = self.jm
+        jm.l10nbase = '/L10N_BASE'
+        jm.outputFormat = 'flat'  # doesn't touch chrome dir without files
+        jarcontents = StringIO('''en-US.jar:
+relativesrcdir dom/locales:
+''')
+        jarcontents.name = 'override.mn'
+        jm.makeJar(jarcontents, '/NO_OUTPUT_REQUIRED')
+        self.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'dom')])
+
+
 if __name__ == '__main__':
     mozunit.main()
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -2861,21 +2861,31 @@ public:
             if (attachment != LOCAL_GL_COLOR_ATTACHMENT0)
                 return mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: attachment", attachment);
 
             mColorAttachment.SetRenderbuffer(wrb);
             break;
         }
 
         mContext->MakeContextCurrent();
-        WebGLuint renderbuffername = wrb ? wrb->GLName() : 0;
+        WebGLuint parambuffername = wrb ? wrb->GLName() : 0;
         if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            mContext->gl->fFramebufferRenderbuffer(target, LOCAL_GL_DEPTH_ATTACHMENT, rbtarget, renderbuffername);
-            mContext->gl->fFramebufferRenderbuffer(target, LOCAL_GL_STENCIL_ATTACHMENT, rbtarget, renderbuffername);
+            WebGLuint depthbuffername = parambuffername;
+            WebGLuint stencilbuffername = parambuffername;
+            if (!parambuffername){
+                depthbuffername   = mDepthAttachment.Renderbuffer()   ? mDepthAttachment.Renderbuffer()->GLName()   : 0;
+                stencilbuffername = mStencilAttachment.Renderbuffer() ? mStencilAttachment.Renderbuffer()->GLName() : 0;
+            }
+            mContext->gl->fFramebufferRenderbuffer(target, LOCAL_GL_DEPTH_ATTACHMENT, rbtarget, depthbuffername);
+            mContext->gl->fFramebufferRenderbuffer(target, LOCAL_GL_STENCIL_ATTACHMENT, rbtarget, stencilbuffername);
         } else {
+            WebGLuint renderbuffername = parambuffername;
+            if(!parambuffername && (attachment == LOCAL_GL_DEPTH_ATTACHMENT || attachment == LOCAL_GL_STENCIL_ATTACHMENT)){
+                renderbuffername = mDepthStencilAttachment.Renderbuffer() ? mDepthStencilAttachment.Renderbuffer()->GLName() : 0;
+            }
             mContext->gl->fFramebufferRenderbuffer(target, attachment, rbtarget, renderbuffername);
         }
     }
 
     void FramebufferTexture2D(WebGLenum target,
                               WebGLenum attachment,
                               WebGLenum textarget,
                               WebGLTexture *wtex,
@@ -2913,21 +2923,31 @@ public:
             if (attachment != LOCAL_GL_COLOR_ATTACHMENT0)
                 return mContext->ErrorInvalidEnumInfo("framebufferTexture2D: attachment", attachment);
 
             mColorAttachment.SetTexture(wtex, level, face);
             break;
         }
 
         mContext->MakeContextCurrent();
-        WebGLuint texturename = wtex ? wtex->GLName() : 0;
+        WebGLuint paramtexturename = wtex ? wtex->GLName() : 0;
         if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            mContext->gl->fFramebufferTexture2D(target, LOCAL_GL_DEPTH_ATTACHMENT, textarget, texturename, level);
-            mContext->gl->fFramebufferTexture2D(target, LOCAL_GL_STENCIL_ATTACHMENT, textarget, texturename, level);
+            WebGLuint depthtexturename = paramtexturename;
+            WebGLuint stenciltexturename = paramtexturename;
+            if(!paramtexturename){
+                depthtexturename   = mDepthAttachment.Texture()   ? mDepthAttachment.Texture()->GLName()   : 0;
+                stenciltexturename = mStencilAttachment.Texture() ? mStencilAttachment.Texture()->GLName() : 0;
+            }
+            mContext->gl->fFramebufferTexture2D(target, LOCAL_GL_DEPTH_ATTACHMENT, textarget, depthtexturename, level);
+            mContext->gl->fFramebufferTexture2D(target, LOCAL_GL_STENCIL_ATTACHMENT, textarget, stenciltexturename, level);
         } else {
+            WebGLuint texturename = paramtexturename;
+            if(!paramtexturename && (attachment == LOCAL_GL_DEPTH_ATTACHMENT || attachment == LOCAL_GL_STENCIL_ATTACHMENT)){
+                texturename = mDepthStencilAttachment.Texture() ? mDepthStencilAttachment.Texture()->GLName() : 0;
+            }
             mContext->gl->fFramebufferTexture2D(target, attachment, textarget, texturename, level);
         }
 
         return;
     }
 
     bool HasIncompleteAttachment() const {
         return (mColorAttachment.IsDefined() && !mColorAttachment.IsComplete()) ||
--- a/content/media/plugins/nsMediaPluginHost.cpp
+++ b/content/media/plugins/nsMediaPluginHost.cpp
@@ -8,16 +8,17 @@
 #include "nsTimeRanges.h"
 #include "MediaResource.h"
 #include "nsHTMLMediaElement.h"
 #include "nsMediaPluginHost.h"
 #include "nsXPCOMStrings.h"
 #include "nsISeekableStream.h"
 #include "pratom.h"
 #include "nsMediaPluginReader.h"
+#include "nsIGfxInfo.h"
 
 #include "MPAPI.h"
 
 using namespace MPAPI;
 using namespace mozilla;
 
 static MediaResource *GetResource(Decoder *aDecoder)
 {
@@ -82,16 +83,39 @@ static PluginHost sPluginHost = {
   GetLength,
   SetMetaDataReadMode,
   SetPlaybackReadMode,
   GetIntPref
 };
 
 void nsMediaPluginHost::TryLoad(const char *name)
 {
+  bool forceEnabled =
+      Preferences::GetBool("stagefright.force-enabled", false);
+  bool disabled =
+      Preferences::GetBool("stagefright.disabled", false);
+
+  if (disabled) {
+    NS_WARNING("XXX stagefright disabled\n");
+    return;
+  }
+
+  if (!forceEnabled) {
+    nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+    if (gfxInfo) {
+      int32_t status;
+      if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_STAGEFRIGHT, &status))) {
+        if (status != nsIGfxInfo::FEATURE_NO_INFO) {
+          NS_WARNING("XXX stagefright blacklisted\n");
+          return;
+        }
+      }
+    }
+  }
+
   PRLibrary *lib = PR_LoadLibrary(name);
   if (lib) {
     Manifest *manifest = static_cast<Manifest *>(PR_FindSymbol(lib, "MPAPI_MANIFEST"));
     if (manifest)
       mPlugins.AppendElement(manifest);
   }
 }
 
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -7,16 +7,17 @@
 #include "AudioContext.h"
 #include "nsContentUtils.h"
 #include "nsIDOMWindow.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "AudioDestinationNode.h"
 #include "AudioBufferSourceNode.h"
 #include "AudioBuffer.h"
+#include "GainNode.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(AudioContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDestination)
@@ -81,11 +82,18 @@ AudioContext::CreateBuffer(JSContext* aJ
   nsRefPtr<AudioBuffer> buffer = new AudioBuffer(this, aLength, aSampleRate);
   if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
   return buffer.forget();
 }
 
+already_AddRefed<GainNode>
+AudioContext::CreateGain()
+{
+  nsRefPtr<GainNode> gainNode = new GainNode(this);
+  return gainNode.forget();
+}
+
 }
 }
 
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -21,16 +21,17 @@ namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class AudioDestinationNode;
 class AudioBufferSourceNode;
 class AudioBuffer;
+class GainNode;
 
 class AudioContext MOZ_FINAL : public nsWrapperCache,
                                public EnableWebAudioCheck
 {
   explicit AudioContext(nsIDOMWindow* aParentWindow);
 
 public:
   virtual ~AudioContext();
@@ -56,16 +57,19 @@ public:
 
   already_AddRefed<AudioBufferSourceNode> CreateBufferSource();
 
   already_AddRefed<AudioBuffer>
   CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
                uint32_t aLength, float aSampleRate,
                ErrorResult& aRv);
 
+  already_AddRefed<GainNode>
+  CreateGain();
+
 private:
   nsCOMPtr<nsIDOMWindow> mWindow;
   nsRefPtr<AudioDestinationNode> mDestination;
 };
 
 }
 }
 
--- a/content/media/webaudio/AudioEventTimeline.h
+++ b/content/media/webaudio/AudioEventTimeline.h
@@ -148,17 +148,29 @@ public:
   void SetValueCurveAtTime(const FloatArrayWrapper& aValues, float aStartTime, float aDuration, ErrorResult& aRv)
   {
     // TODO: implement
     // InsertEvent(Event(Event::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues), aRv);
   }
 
   void CancelScheduledValues(float aStartTime)
   {
-    // TODO: implement
+    for (unsigned i = 0; i < mEvents.Length(); ++i) {
+      if (mEvents[i].mTime >= aStartTime) {
+#ifdef DEBUG
+        // Sanity check: the array should be sorted, so all of the following
+        // events should have a time greater than aStartTime too.
+        for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
+          MOZ_ASSERT(mEvents[j].mTime >= aStartTime);
+        }
+#endif
+        mEvents.TruncateLength(i);
+        break;
+      }
+    }
   }
 
   // This method computes the AudioParam value at a given time based on the event timeline
   float GetValueAtTime(float aTime) const
   {
     const Event* previous = nullptr;
     const Event* next = nullptr;
 
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/GainNode.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GainNode.h"
+#include "mozilla/dom/GainNodeBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(GainNode)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GainNode, AudioNode)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mGain)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GainNode, AudioNode)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR(tmp->mGain, AudioParam, "gain value")
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GainNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(GainNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(GainNode, AudioNode)
+
+GainNode::GainNode(AudioContext* aContext)
+  : AudioNode(aContext)
+  , mGain(new AudioParam(aContext, 1.0f, 0.0f, 1.0f))
+{
+}
+
+JSObject*
+GainNode::WrapObject(JSContext* aCx, JSObject* aScope,
+                     bool* aTriedToWrap)
+{
+  return GainNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
+}
+
+}
+}
+
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/GainNode.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GainNode_h_
+#define GainNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+
+namespace mozilla {
+namespace dom {
+
+class AudioContext;
+
+class GainNode : public AudioNode
+{
+public:
+  explicit GainNode(AudioContext* aContext);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GainNode, AudioNode)
+
+  virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
+                               bool* aTriedToWrap);
+
+  virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
+  {
+    return 1;
+  }
+  virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
+  {
+    return 1;
+  }
+
+  AudioParam* Gain() const
+  {
+    return mGain;
+  }
+
+private:
+  nsRefPtr<AudioParam> mGain;
+};
+
+}
+}
+
+#endif
+
--- a/content/media/webaudio/Makefile.in
+++ b/content/media/webaudio/Makefile.in
@@ -18,26 +18,28 @@ CPPSRCS := \
   AudioBuffer.cpp \
   AudioBufferSourceNode.cpp \
   AudioContext.cpp \
   AudioDestinationNode.cpp \
   AudioNode.cpp \
   AudioParam.cpp \
   AudioSourceNode.cpp \
   EnableWebAudioCheck.cpp \
+  GainNode.cpp \
   $(NULL)
 
 EXPORTS_NAMESPACES := mozilla/dom
 EXPORTS_mozilla/dom := \
   AudioBuffer.h \
   AudioBufferSourceNode.h \
   AudioDestinationNode.h \
   AudioNode.h \
   AudioParam.h \
   AudioSourceNode.h \
+  GainNode.h \
   $(NULL)
 
 PARALLEL_DIRS := test
 
 ifdef ENABLE_TESTS
 TOOL_DIRS += compiledtest
 endif
 
--- a/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
+++ b/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
@@ -203,16 +203,35 @@ void TestEventReplacement()
   is(timeline.GetEventCount(), 1, "Event should be replaced");
   is(timeline.GetValueAtTime(0.1f), 20.0f, "The first event should be overwritten");
   timeline.LinearRampToValueAtTime(30.0f, 0.1f, rv);
   is(rv, NS_OK, "Event scheduling should be successful");
   is(timeline.GetEventCount(), 2, "Different event type should be appended");
   is(timeline.GetValueAtTime(0.1f), 30.0f, "The first event should be overwritten");
 }
 
+void TestEventRemoval()
+{
+  Timeline timeline(10.0f, .1f, 20.0f);
+
+  ErrorResultMock rv;
+
+  timeline.SetValueAtTime(10.0f, 0.1f, rv);
+  timeline.SetValueAtTime(15.0f, 0.15f, rv);
+  timeline.SetValueAtTime(20.0f, 0.2f, rv);
+  timeline.LinearRampToValueAtTime(30.0f, 0.3f, rv);
+  is(timeline.GetEventCount(), 4, "Should have three events initially");
+  timeline.CancelScheduledValues(0.4f);
+  is(timeline.GetEventCount(), 4, "Trying to delete past the end of the array should have no effect");
+  timeline.CancelScheduledValues(0.3f);
+  is(timeline.GetEventCount(), 3, "Should successfully delete one event");
+  timeline.CancelScheduledValues(0.12f);
+  is(timeline.GetEventCount(), 1, "Should successfully delete two events");
+}
+
 void TestBeforeFirstEvent()
 {
   Timeline timeline(10.0f, .1f, 20.0f);
 
   ErrorResultMock rv;
 
   timeline.SetValueAtTime(20.0f, 1.0f, rv);
   is(timeline.GetValueAtTime(0.5f), 10.0f, "Retrun the default value before the first event");
@@ -322,16 +341,17 @@ int main()
   ScopedXPCOM xpcom("TestAudioEventTimeline");
   if (xpcom.failed()) {
     return 1;
   }
 
   TestSpecExample();
   TestInvalidEvents();
   TestEventReplacement();
+  TestEventRemoval();
   TestBeforeFirstEvent();
   TestAfterLastValueEvent();
   TestAfterLastTargetValueEvent();
   TestAfterLastTargetValueEventWithValueSet();
   TestValue();
   TestLinearRampAtZero();
   TestExponentialRampAtZero();
   TestLinearRampAtSameTime();
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -9,12 +9,13 @@ VPATH          := @srcdir@
 relativesrcdir := @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_FILES := \
   test_AudioBuffer.html \
   test_AudioContext.html \
   test_badConnect.html \
+  test_gainNode.html \
   test_singleSourceDest.html \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_gainNode.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GainNode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  SpecialPowers.setBoolPref("media.webaudio.enabled", true);
+
+  var context = new mozAudioContext();
+  var buffer = context.createBuffer(1, 2048, 44100);
+  for (var i = 0; i < 2048; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+  }
+
+  var destination = context.destination;
+
+  var source = context.createBufferSource();
+
+  var gain = context.createGain();
+
+  source.buffer = buffer;
+
+  source.connect(gain);
+  gain.connect(destination);
+
+  ok(gain.gain, "The audioparam member must exist");
+  is(gain.gain.value, 1.0, "Correct initial value");
+  is(gain.gain.defaultValue, 1.0, "Correct default value");
+  is(gain.gain.minValue, 0, "Correct min value");
+  is(gain.gain.maxValue, 1.0, "Correct max value");
+  gain.gain.value = 0.5;
+  is(gain.gain.value, 0.5, "Correct initial value");
+  is(gain.gain.defaultValue, 1.0, "Correct default value");
+  is(gain.gain.minValue, 0, "Correct min value");
+  is(gain.gain.maxValue, 1.0, "Correct max value");
+
+  source.start(0);
+  SimpleTest.executeSoon(function() {
+    source.stop(0);
+    source.disconnect();
+    gain.disconnect();
+
+    SpecialPowers.clearUserPref("media.webaudio.enabled");
+    SimpleTest.finish();
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -506,39 +506,38 @@ NS_IMETHODIMP
 nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
                                  float aX,
                                  float aY,
                                  int32_t aButton,
                                  int32_t aClickCount,
                                  int32_t aModifiers,
                                  bool aIgnoreRootScrollFrame,
                                  float aPressure,
-                                 unsigned short aInputSourceArg,
-                                 bool *aPreventDefault)
+                                 unsigned short aInputSourceArg)
 {
   return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
                               aIgnoreRootScrollFrame, aPressure,
-                              aInputSourceArg, false, aPreventDefault);
+                              aInputSourceArg, false);
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendMouseEventToWindow(const nsAString& aType,
                                          float aX,
                                          float aY,
                                          int32_t aButton,
                                          int32_t aClickCount,
                                          int32_t aModifiers,
                                          bool aIgnoreRootScrollFrame,
                                          float aPressure,
                                          unsigned short aInputSourceArg)
 {
   SAMPLE_LABEL("nsDOMWindowUtils", "SendMouseEventToWindow");
   return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
                               aIgnoreRootScrollFrame, aPressure,
-                              aInputSourceArg, true, nullptr);
+                              aInputSourceArg, true);
 }
 
 static nsIntPoint
 ToWidgetPoint(float aX, float aY, const nsPoint& aOffset,
               nsPresContext* aPresContext)
 {
   double appPerDev = aPresContext->AppUnitsPerDevPixel();
   nscoord appPerCSS = nsPresContext::AppUnitsPerCSSPixel();
@@ -551,18 +550,17 @@ nsDOMWindowUtils::SendMouseEventCommon(c
                                        float aX,
                                        float aY,
                                        int32_t aButton,
                                        int32_t aClickCount,
                                        int32_t aModifiers,
                                        bool aIgnoreRootScrollFrame,
                                        float aPressure,
                                        unsigned short aInputSourceArg,
-                                       bool aToWindow,
-                                       bool *aPreventDefault)
+                                       bool aToWindow)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // get the widget to send the event to
   nsPoint offset;
   nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
@@ -579,19 +577,17 @@ nsDOMWindowUtils::SendMouseEventCommon(c
     msg = NS_MOUSE_MOVE;
   else if (aType.EqualsLiteral("mouseover"))
     msg = NS_MOUSE_ENTER;
   else if (aType.EqualsLiteral("mouseout"))
     msg = NS_MOUSE_EXIT;
   else if (aType.EqualsLiteral("contextmenu")) {
     msg = NS_CONTEXTMENU;
     contextMenuKey = (aButton == 0);
-  } else if (aType.EqualsLiteral("MozMouseHittest"))
-    msg = NS_MOUSE_MOZHITTEST;
-  else
+  } else
     return NS_ERROR_FAILURE;
 
   if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
     aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
   }
 
   nsMouseEvent event(true, msg, widget, nsMouseEvent::eReal,
                      contextMenuKey ?
@@ -622,20 +618,17 @@ nsDOMWindowUtils::SendMouseEventCommon(c
       return NS_ERROR_FAILURE;
     nsIView* view = viewManager->GetRootView();
     if (!view)
       return NS_ERROR_FAILURE;
 
     status = nsEventStatus_eIgnore;
     return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
   }
-  nsresult rv = widget->DispatchEvent(&event, status);
-  *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
-
-  return rv;
+  return widget->DispatchEvent(&event, status);
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendWheelEvent(float aX,
                                  float aY,
                                  double aDeltaX,
                                  double aDeltaY,
                                  double aDeltaZ,
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -39,13 +39,12 @@ protected:
                                   float aX,
                                   float aY,
                                   int32_t aButton,
                                   int32_t aClickCount,
                                   int32_t aModifiers,
                                   bool aIgnoreRootScrollFrame,
                                   float aPressure,
                                   unsigned short aInputSourceArg,
-                                  bool aToWindow,
-                                  bool *aPreventDefault);
+                                  bool aToWindow);
 
   static mozilla::widget::Modifiers GetWidgetModifiers(int32_t aModifiers);
 };
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1540,16 +1540,17 @@ nsJSContext::CompileScript(const PRUnich
                            int32_t aTextLength,
                            nsIPrincipal *aPrincipal,
                            const char *aURL,
                            uint32_t aLineNo,
                            uint32_t aVersion,
                            nsScriptObjectHolder<JSScript>& aScriptObject,
                            bool aSaveSource /* = false */)
 {
+  SAMPLE_LABEL_PRINTF("JS", "Compile Script", "%s", aURL ? aURL : "");
   NS_ENSURE_TRUE(mIsInitialized, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG_POINTER(aPrincipal);
 
   JSObject* scopeObject = ::JS_GetGlobalObject(mContext);
   xpc_UnmarkGrayObject(scopeObject);
 
   bool ok = false;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -197,16 +197,21 @@ DOMInterfaces = {
     'workers': True,
 },
 
 'FormData': [
 {
     'workers': True,
 }],
 
+'GainNode': [
+{
+    'resultNotAddRefed': [ 'gain' ],
+}],
+
 'HTMLCollection': [
 {
     'nativeType': 'nsIHTMLCollection',
     'resultNotAddRefed': [ 'item' ]
 }],
 
 'HTMLOptionsCollection': [
 {
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -2382,55 +2382,16 @@ BluetoothDBusService::Disconnect(const u
   // Currently, just fire success because Disconnect() doesn't fail, 
   // but we still make aRunnable pass into this function for future
   // once Disconnect will fail.
   nsString replyError;
   BluetoothValue v = true;
   DispatchBluetoothReply(aRunnable, v, replyError);
 }
 
-class CreateBluetoothScoSocket : public nsRunnable
-{
-public:
-  CreateBluetoothScoSocket(UnixSocketConsumer* aConsumer,
-                           const nsAString& aAddress,
-                           bool aAuth,
-                           bool aEncrypt)
-    : mConsumer(aConsumer),
-      mAddress(aAddress),
-      mAuth(aAuth),
-      mEncrypt(aEncrypt)
-  {
-  }
-
-  nsresult
-  Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    nsString replyError;
-    BluetoothUnixSocketConnector* c =
-      new BluetoothUnixSocketConnector(BluetoothSocketType::SCO, -1,
-                                       mAuth, mEncrypt);
-
-    if (!mConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(mAddress).get())) {
-      replyError.AssignLiteral("SocketConnectionError");
-      return NS_ERROR_FAILURE;
-    }
-
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<UnixSocketConsumer> mConsumer;
-  nsString mAddress;
-  bool mAuth;
-  bool mEncrypt;
-};
-
 class ConnectBluetoothSocketRunnable : public nsRunnable
 {
 public:
   ConnectBluetoothSocketRunnable(BluetoothReplyRunnable* aRunnable,
                                  UnixSocketConsumer* aConsumer,
                                  const nsAString& aObjectPath,
                                  const nsAString& aServiceUUID,
                                  BluetoothSocketType aType,
@@ -2578,22 +2539,23 @@ BluetoothDBusService::GetScoSocket(const
                                    mozilla::ipc::UnixSocketConsumer* aConsumer)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
   if (!mConnection || !gThreadConnection) {
     NS_ERROR("Bluetooth service not started yet!");
     return NS_ERROR_FAILURE;
   }
 
-  nsRefPtr<nsRunnable> func(new CreateBluetoothScoSocket(aConsumer,
-                                                         aAddress,
-                                                         aAuth,
-                                                         aEncrypt));
-  if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
-    NS_WARNING("Cannot dispatch firmware loading task!");
+  nsString replyError;
+  BluetoothUnixSocketConnector* c =
+    new BluetoothUnixSocketConnector(BluetoothSocketType::SCO, -1,
+                                     aAuth, aEncrypt);
+
+  if (!aConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(aAddress).get())) {
+    replyError.AssignLiteral("SocketConnectionError");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 bool
 BluetoothDBusService::SendFile(const nsAString& aDeviceAddress,
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -35,17 +35,17 @@ interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 
-[scriptable, uuid(b3a3589d-cc9d-4123-9b21-51c66e88b436)]
+[scriptable, uuid(C98B7275-93C4-4EAD-B7CF-573D872C1071)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -199,18 +199,17 @@ interface nsIDOMWindowUtils : nsISupport
   const long MODIFIER_CAPSLOCK   = 0x0020;
   const long MODIFIER_FN         = 0x0040;
   const long MODIFIER_NUMLOCK    = 0x0080;
   const long MODIFIER_SCROLLLOCK = 0x0100;
   const long MODIFIER_SYMBOLLOCK = 0x0200;
   const long MODIFIER_OS         = 0x0400;
 
   /** Synthesize a mouse event. The event types supported are:
-   *    mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu,
-   *    MozMouseHitTest
+   *    mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Note that additional events may be fired as a result of this call. For
    * instance, typically a click event will be fired as a result of a
    * mousedown and mouseup in sequence.
    *
    * Normally at this level of events, the mouseover and mouseout events are
@@ -232,28 +231,26 @@ interface nsIDOMWindowUtils : nsISupport
    * @param aButton button to synthesize
    * @param aClickCount number of clicks that have been performed
    * @param aModifiers modifiers pressed, using constants defined as MODIFIER_*
    * @param aIgnoreRootScrollFrame whether the event should ignore viewport bounds
    *                           during dispatch
    * @param aPressure touch input pressure: 0.0 -> 1.0
    * @param aInputSourceArg input source, see nsIDOMMouseEvent for values,
    *        defaults to mouse input.
-   *
-   * returns true if the page called prevent default on this event
    */
-  boolean sendMouseEvent(in AString aType,
-                         in float aX,
-                         in float aY,
-                         in long aButton,
-                         in long aClickCount,
-                         in long aModifiers,
-                         [optional] in boolean aIgnoreRootScrollFrame,
-                         [optional] in float aPressure,
-                         [optional] in unsigned short aInputSourceArg);
+  void sendMouseEvent(in AString aType,
+                      in float aX,
+                      in float aY,
+                      in long aButton,
+                      in long aClickCount,
+                      in long aModifiers,
+                      [optional] in boolean aIgnoreRootScrollFrame,
+                      [optional] in float aPressure,
+                      [optional] in unsigned short aInputSourceArg);
 
   /** Synthesize a touch event. The event types supported are:
    *    touchstart, touchend, touchmove, and touchcancel
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1271,19 +1271,18 @@ TabChild::RecvMouseEvent(const nsString&
                          const float&    aY,
                          const int32_t&  aButton,
                          const int32_t&  aClickCount,
                          const int32_t&  aModifiers,
                          const bool&     aIgnoreRootScrollFrame)
 {
   nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
   NS_ENSURE_TRUE(utils, true);
-  bool ignored = false;
   utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
-                        aIgnoreRootScrollFrame, 0, 0, &ignored);
+                        aIgnoreRootScrollFrame, 0, 0);
   return true;
 }
 
 bool
 TabChild::RecvRealMouseEvent(const nsMouseEvent& event)
 {
   nsMouseEvent localEvent(event);
   DispatchWidgetEvent(localEvent);
--- a/dom/settings/SettingsManager.js
+++ b/dom/settings/SettingsManager.js
@@ -132,17 +132,18 @@ SettingsLock.prototype = {
     lock._open = true;
   },
 
   createTransactionAndProcess: function() {
     if (this._settingsManager._settingsDB._db) {
       var lock;
       while (lock = this._settingsManager._locks.dequeue()) {
         if (!lock._transaction) {
-          lock._transaction = lock._settingsManager._settingsDB._db.transaction(SETTINGSSTORE_NAME, "readwrite");
+          let transactionType = this._settingsManager.hasWritePrivileges ? "readwrite" : "readonly";
+          lock._transaction = lock._settingsManager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType);
         }
         lock.process();
       }
       if (!this._requests.isEmpty())
         this.process();
     }
   },
 
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -20,12 +20,15 @@ interface mozAudioContext {
 
     // [Creator, Throws]
     // AudioBuffer createBuffer(ArrayBuffer buffer, boolean mixToMono);
 
     // AudioNode creation 
     [Creator]
     AudioBufferSourceNode createBufferSource();
 
+    [Creator]
+    GainNode createGain();
+
 };
 
 typedef mozAudioContext AudioContext;
 
--- a/dom/webidl/AudioParam.webidl
+++ b/dom/webidl/AudioParam.webidl
@@ -32,12 +32,12 @@ interface AudioParam {
     void setTargetAtTime(float target, float startTime, float timeConstant);
 
     // Sets an array of arbitrary parameter values starting at time for the given duration. 
     // The number of values will be scaled to fit into the desired duration. 
     // [Throws]
     // void setValueCurveAtTime(Float32Array values, float startTime, float duration);
 
     // Cancels all scheduled parameter changes with times greater than or equal to startTime. 
-    // void cancelScheduledValues(float startTime);
+    void cancelScheduledValues(float startTime);
 
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/GainNode.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[PrefControlled]
+interface GainNode : AudioNode {
+
+    readonly attribute AudioParam gain;
+
+};
+
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -24,16 +24,17 @@ webidl_files = \
   DOMTokenList.webidl \
   DOMSettableTokenList.webidl \
   Function.webidl \
   EventHandler.webidl \
   EventListener.webidl \
   EventTarget.webidl \
   FileList.webidl \
   FileReaderSync.webidl \
+  GainNode.webidl \
   HTMLCollection.webidl \
   HTMLOptionsCollection.webidl \
   HTMLPropertiesCollection.webidl \
   ImageData.webidl \
   NodeList.webidl \
   PaintRequestList.webidl \
   Performance.webidl \
   PerformanceNavigation.webidl \
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -916,17 +916,17 @@ void ExtractFontsFromJar(nsIFile* aLocal
 
     nsRefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
     nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
 
     Omnijar::GetURIString(Omnijar::Type::GRE, jarPath);
     jarFile->GetLastModifiedTime(&jarModifiedTime);
 
     mozilla::scache::StartupCache* cache = mozilla::scache::StartupCache::GetSingleton();
-    if (NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize))
+    if (cache && NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize))
         && longSize == sizeof(int64_t)) {
         if (jarModifiedTime < *((int64_t*) cachedModifiedTimeBuf)) {
             return;
         }
     }
 
     aLocalDir->Exists(&exists);
     if (!exists) {
@@ -974,17 +974,17 @@ void ExtractFontsFromJar(nsIFile* aLocal
                 spec.Append(path);
                 if (NS_FAILED(CopyFromUriToFile(spec, localFile))) {
                     localFile->Remove(true);
                     allFontsExtracted = false;
                 }
             }
         }
     }
-    if (allFontsExtracted) {
+    if (allFontsExtracted && cache) {
         cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&jarModifiedTime, sizeof(int64_t));
     }
 }
 
 #endif
 
 void
 gfxFT2FontList::FindFonts()
--- a/js/src/config/config.mk
+++ b/js/src/config/config.mk
@@ -698,27 +698,31 @@ else
 endif
 
 EXPAND_LOCALE_SRCDIR = $(if $(filter en-US,$(AB_CD)),$(topsrcdir)/$(1)/en-US,$(call core_realpath,$(L10NBASEDIR))/$(AB_CD)/$(subst /locales,,$(1)))
 
 ifdef relativesrcdir
 LOCALE_SRCDIR = $(call EXPAND_LOCALE_SRCDIR,$(relativesrcdir))
 endif
 
-ifdef LOCALE_SRCDIR
-# if LOCALE_MERGEDIR is set, use mergedir first, then the localization,
-# and finally en-US
+ifdef relativesrcdir
+MAKE_JARS_FLAGS += --relativesrcdir=$(relativesrcdir)
+ifneq (en-US,$(AB_CD))
 ifdef LOCALE_MERGEDIR
-MAKE_JARS_FLAGS += -c $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir))
+MAKE_JARS_FLAGS += --locale-mergedir=$(LOCALE_MERGEDIR)
 endif
+ifdef IS_LANGUAGE_REPACK
+MAKE_JARS_FLAGS += --l10n-base=$(L10NBASEDIR)
+endif
+else
 MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR)
-ifdef LOCALE_MERGEDIR
-MAKE_JARS_FLAGS += -c $(topsrcdir)/$(relativesrcdir)/en-US
-endif
-endif
+endif # en-US
+else
+MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR)
+endif # ! relativesrcdir
 
 ifdef LOCALE_MERGEDIR
 MERGE_FILE = $(firstword \
   $(wildcard $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir))/$(1)) \
   $(wildcard $(LOCALE_SRCDIR)/$(1)) \
   $(srcdir)/en-US/$(1) )
 else
 MERGE_FILE = $(LOCALE_SRCDIR)/$(1)
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -43,16 +43,17 @@ using namespace js::gc;
 JSCompartment::JSCompartment(JSRuntime *rt)
   : rt(rt),
     principals(NULL),
     global_(NULL),
 #ifdef JSGC_GENERATIONAL
     gcStoreBuffer(&gcNursery),
 #endif
     needsBarrier_(false),
+    ionUsingBarriers_(false),
     gcScheduled(false),
     gcState(NoGC),
     gcPreserveCode(false),
     gcBytes(0),
     gcTriggerBytes(0),
     gcHeapGrowthFactor(3.0),
     gcNextCompartment(NULL),
     hold(false),
@@ -126,28 +127,30 @@ JSCompartment::init(JSContext *cx)
         gcStoreBuffer.disable();
     }
 #endif
 
     return debuggees.init();
 }
 
 void
-JSCompartment::setNeedsBarrier(bool needs)
+JSCompartment::setNeedsBarrier(bool needs, ShouldUpdateIon updateIon)
 {
 #ifdef JS_METHODJIT
     /* ClearAllFrames calls compileBarriers() and needs the old value. */
     bool old = compileBarriers();
     if (compileBarriers(needs) != old)
         mjit::ClearAllFrames(this);
 #endif
 
 #ifdef JS_ION
-    if (needsBarrier_ != needs)
+    if (updateIon == UpdateIon && needs != ionUsingBarriers_) {
         ion::ToggleBarriers(this, needs);
+        ionUsingBarriers_ = needs;
+    }
 #endif
 
     needsBarrier_ = needs;
 }
 
 #ifdef JS_ION
 bool
 JSCompartment::ensureIonCompartmentExists(JSContext *cx)
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -150,31 +150,37 @@ struct JSCompartment
 
 #ifdef JSGC_GENERATIONAL
     js::gc::Nursery              gcNursery;
     js::gc::StoreBuffer          gcStoreBuffer;
 #endif
 
   private:
     bool                         needsBarrier_;
+    bool                         ionUsingBarriers_;
   public:
 
     bool needsBarrier() const {
         return needsBarrier_;
     }
 
     bool compileBarriers(bool needsBarrier) const {
         return needsBarrier || rt->gcZeal() == js::gc::ZealVerifierPreValue;
     }
 
     bool compileBarriers() const {
         return compileBarriers(needsBarrier());
     }
 
-    void setNeedsBarrier(bool needs);
+    enum ShouldUpdateIon {
+        DontUpdateIon,
+        UpdateIon
+    };
+
+    void setNeedsBarrier(bool needs, ShouldUpdateIon updateIon);
 
     static size_t OffsetOfNeedsBarrier() {
         return offsetof(JSCompartment, needsBarrier_);
     }
 
     js::GCMarker *barrierTracer() {
         JS_ASSERT(needsBarrier_);
         return &rt->gcMarker;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4074,17 +4074,17 @@ ResetIncrementalGC(JSRuntime *rt, const 
         return;
 
     /* Cancel and ongoing marking. */
     bool wasMarking = false;
     {
         AutoCopyFreeListToArenas copy(rt);
         for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
             if (c->isGCMarking()) {
-                c->setNeedsBarrier(false);
+                c->setNeedsBarrier(false, JSCompartment::DontUpdateIon);
                 c->setGCState(JSCompartment::NoGC);
                 wasMarking = true;
             }
         }
     }
 
     if (wasMarking)
         rt->gcMarker.reset();
@@ -4132,35 +4132,40 @@ AutoGCSlice::AutoGCSlice(JSRuntime *rt)
      * During incremental GC, the compartment's active flag determines whether
      * there are stack frames active for any of its scripts. Normally this flag
      * is set at the beginning of the mark phase. During incremental GC, we also
      * set it at the start of every phase.
      */
     rt->stackSpace.markActiveCompartments();
 
     for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
-        /* Clear this early so we don't do any write barriers during GC. */
+        /*
+         * Clear needsBarrier early so we don't do any write barriers during
+         * GC. We don't need to update the Ion barriers (which is expensive)
+         * because Ion code doesn't run during GC. If need be, we'll update the
+         * Ion barriers in ~AutoGCSlice.
+         */
         if (c->isGCMarking()) {
             JS_ASSERT(c->needsBarrier());
-            c->setNeedsBarrier(false);
+            c->setNeedsBarrier(false, JSCompartment::DontUpdateIon);
         } else {
             JS_ASSERT(!c->needsBarrier());
         }
     }
 }
 
 AutoGCSlice::~AutoGCSlice()
 {
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
         if (c->isGCMarking()) {
-            c->setNeedsBarrier(true);
+            c->setNeedsBarrier(true, JSCompartment::UpdateIon);
             c->arenas.prepareForIncrementalGC(runtime);
         } else {
             JS_ASSERT(c->isGCSweeping());
-            c->setNeedsBarrier(false);
+            c->setNeedsBarrier(false, JSCompartment::UpdateIon);
         }
     }
 }
 
 static void
 PushZealSelectedObjects(JSRuntime *rt)
 {
 #ifdef JS_GC_ZEAL
@@ -4307,17 +4312,17 @@ IncrementalCollectSlice(JSRuntime *rt,
         if (rt->gcSweepOnBackgroundThread)
             rt->gcHelperThread.startBackgroundSweep(gckind == GC_SHRINK);
 
         rt->gcIncrementalState = NO_INCREMENTAL;
         break;
 
       default:
         JS_ASSERT(false);
-     }
+    }
 }
 
 class IncrementalSafety
 {
     const char *reason_;
 
     IncrementalSafety(const char *reason) : reason_(reason) {}
 
@@ -5262,17 +5267,17 @@ StartVerifyPreBarriers(JSRuntime *rt)
         node = NextNode(node);
     }
 
     rt->gcVerifyPreData = trc;
     rt->gcIncrementalState = MARK;
     rt->gcMarker.start(rt);
     for (CompartmentsIter c(rt); !c.done(); c.next()) {
         PurgeJITCaches(c);
-        c->setNeedsBarrier(true);
+        c->setNeedsBarrier(true, JSCompartment::UpdateIon);
         c->arenas.purge();
     }
 
     return;
 
 oom:
     rt->gcIncrementalState = NO_INCREMENTAL;
     trc->~VerifyPreTracer();
@@ -5345,17 +5350,17 @@ EndVerifyPreBarriers(JSRuntime *rt)
     bool compartmentCreated = false;
 
     /* We need to disable barriers before tracing, which may invoke barriers. */
     for (CompartmentsIter c(rt); !c.done(); c.next()) {
         if (!c->needsBarrier())
             compartmentCreated = true;
 
         PurgeJITCaches(c);
-        c->setNeedsBarrier(false);
+        c->setNeedsBarrier(false, JSCompartment::UpdateIon);
     }
 
     /*
      * We need to bump gcNumber so that the methodjit knows that jitcode has
      * been discarded.
      */
     JS_ASSERT(trc->number == rt->gcNumber);
     rt->gcNumber++;
--- a/mobile/android/base/AboutHomeContent.java
+++ b/mobile/android/base/AboutHomeContent.java
@@ -2,18 +2,20 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.db.BrowserContract.Images;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
+import org.mozilla.gecko.util.GeckoAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
@@ -54,18 +56,20 @@ import android.widget.SimpleCursorAdapte
 import android.widget.TextView;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.EnumSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public class AboutHomeContent extends ScrollView
                               implements TabsAccessor.OnQueryTabsCompleteListener,
                                          LightweightTheme.OnChangeListener {
     private static final String LOGTAG = "GeckoAboutHome";
@@ -258,41 +262,134 @@ public class AboutHomeContent extends Sc
         mCursor = BrowserDB.getTopSites(resolver, mNumberOfTopSites);
 
         post(new Runnable() {
             public void run() {
                 if (mTopSitesAdapter == null) {
                     mTopSitesAdapter = new TopSitesCursorAdapter(mActivity,
                                                                  R.layout.abouthome_topsite_item,
                                                                  mCursor,
-                                                                 new String[] { URLColumns.TITLE,
-                                                                                URLColumns.THUMBNAIL },
-                                                                 new int[] { R.id.title, R.id.thumbnail });
+                                                                 new String[] { URLColumns.TITLE },
+                                                                 new int[] { R.id.title });
 
                     mTopSitesAdapter.setViewBinder(new TopSitesViewBinder());
                     mTopSitesGrid.setAdapter(mTopSitesAdapter);
                 } else {
                     mTopSitesAdapter.changeCursor(mCursor);
                 }
 
+                if (mTopSitesAdapter.getCount() > 0)
+                    loadTopSitesThumbnails(resolver);
+
                 updateLayout(syncIsSetup);
 
                 // Free the old Cursor in the right thread now.
                 if (oldCursor != null && !oldCursor.isClosed())
                     oldCursor.close();
 
                 // Even if AboutHome isn't necessarily entirely loaded if we
                 // get here, for phones this is the part the user initially sees,
                 // so it's the one we will care about for now.
                 if (mLoadCompleteCallback != null)
                     mLoadCompleteCallback.callback();
             }
         });
     }
 
+    private List<String> getTopSitesUrls() {
+        List<String> urls = new ArrayList<String>();
+
+        Cursor c = mTopSitesAdapter.getCursor();
+        if (c == null || !c.moveToFirst())
+            return urls;
+
+        do {
+            final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
+            urls.add(url);
+        } while (c.moveToNext());
+
+        return urls;
+    }
+
+    private void displayThumbnail(View view, Bitmap thumbnail) {
+        ImageView thumbnailView = (ImageView) view.findViewById(R.id.thumbnail);
+
+        if (thumbnail == null) {
+            thumbnailView.setImageResource(R.drawable.abouthome_thumbnail_bg);
+            thumbnailView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        } else {
+            try {
+                thumbnailView.setImageBitmap(thumbnail);
+                thumbnailView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+            } catch (OutOfMemoryError oom) {
+                Log.e(LOGTAG, "Unable to load thumbnail bitmap", oom);
+                thumbnailView.setImageResource(R.drawable.abouthome_thumbnail_bg);
+                thumbnailView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+            }
+        }
+    }
+
+    private void updateTopSitesThumbnails(Map<String, Bitmap> thumbnails) {
+        for (int i = 0; i < mTopSitesGrid.getChildCount(); i++) {
+            final View view = mTopSitesGrid.getChildAt(i);
+
+            Cursor c = (Cursor) mTopSitesGrid.getItemAtPosition(i);
+            final String url = c.getString(c.getColumnIndex(URLColumns.URL));
+
+            displayThumbnail(view, thumbnails.get(url));
+        }
+
+        mTopSitesGrid.invalidate();
+    }
+
+    public Map<String, Bitmap> getTopSitesThumbnails(Cursor c) {
+        Map<String, Bitmap> thumbnails = new HashMap<String, Bitmap>();
+
+        try {
+            if (c == null || !c.moveToFirst())
+                return thumbnails;
+
+            do {
+                final String url = c.getString(c.getColumnIndexOrThrow(Images.URL));
+                final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Images.THUMBNAIL));
+                if (b == null)
+                    continue;
+
+                Bitmap thumbnail = BitmapFactory.decodeByteArray(b, 0, b.length);
+                if (thumbnail == null)
+                    continue;
+
+                thumbnails.put(url, thumbnail);
+            } while (c.moveToNext());
+        } finally {
+            if (c != null)
+                c.close();
+        }
+
+        return thumbnails;
+    }
+
+    private void loadTopSitesThumbnails(final ContentResolver cr) {
+        final List<String> urls = getTopSitesUrls();
+        if (urls.size() == 0)
+            return;
+
+        (new GeckoAsyncTask<Void, Void, Cursor>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
+            @Override
+            public Cursor doInBackground(Void... params) {
+                return BrowserDB.getThumbnailsForUrls(cr, urls);
+            }
+
+            @Override
+            public void onPostExecute(Cursor c) {
+                updateTopSitesThumbnails(getTopSitesThumbnails(c));
+            }
+        }).execute();
+    }
+
     void update(final EnumSet<UpdateFlags> flags) {
         GeckoAppShell.getHandler().post(new Runnable() {
             public void run() {
                 if (flags.contains(UpdateFlags.TOP_SITES))
                     loadTopSites();
 
                 if (flags.contains(UpdateFlags.PREVIOUS_TABS))
                     readLastTabs();
@@ -670,38 +767,16 @@ public class AboutHomeContent extends Sc
         public void bindView(View view, Context context, Cursor cursor) {
             super.bindView(view, context, cursor);
             view.setLayoutParams(new AbsListView.LayoutParams(mTopSitesGrid.getColumnWidth(),
                                                             Math.round(mTopSitesGrid.getColumnWidth()*Tabs.getThumbnailAspectRatio())));
         }
     }
 
     class TopSitesViewBinder implements SimpleCursorAdapter.ViewBinder {
-        private boolean updateThumbnail(View view, Cursor cursor, int thumbIndex) {
-            byte[] b = cursor.getBlob(thumbIndex);
-            ImageView thumbnail = (ImageView) view;
-
-            if (b == null) {
-                thumbnail.setImageResource(R.drawable.abouthome_thumbnail_bg);
-                thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER);
-            } else {
-                try {
-                    Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
-                    thumbnail.setImageBitmap(bitmap);
-                    thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP);
-                } catch (OutOfMemoryError oom) {
-                    Log.e(LOGTAG, "Unable to load thumbnail bitmap", oom);
-                    thumbnail.setImageResource(R.drawable.abouthome_thumbnail_bg);
-                    thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER);
-                }
-            }
-
-            return true;
-        }
-
         private boolean updateTitle(View view, Cursor cursor, int titleIndex) {
             String title = cursor.getString(titleIndex);
             TextView titleView = (TextView) view;
 
             // Use the URL instead of an empty title for consistency with the normal URL
             // bar view - this is the equivalent of getDisplayTitle() in Tab.java
             if (title == null || title.length() == 0) {
                 int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
@@ -714,18 +789,13 @@ public class AboutHomeContent extends Sc
 
         @Override
         public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
             int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE);
             if (columnIndex == titleIndex) {
                 return updateTitle(view, cursor, titleIndex);
             }
 
-            int thumbIndex = cursor.getColumnIndexOrThrow(URLColumns.THUMBNAIL);
-            if (columnIndex == thumbIndex) {
-                return updateThumbnail(view, cursor, thumbIndex);
-            }
-
             // Other columns are handled automatically
             return false;
         }
     }
 }
--- a/mobile/android/base/Favicons.java
+++ b/mobile/android/base/Favicons.java
@@ -2,28 +2,30 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.util.GeckoJarReader;
+import org.mozilla.gecko.util.LruCache;
 
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.entity.BufferedHttpEntity;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
+import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;
 import android.util.Log;
 
 import java.io.InputStream;
 import java.net.MalformedURLException;
@@ -41,16 +43,17 @@ public class Favicons {
 
     public static final long NOT_LOADING = 0;
 
     private Context mContext;
     private DatabaseHelper mDbHelper;
 
     private Map<Long,LoadFaviconTask> mLoadTasks;
     private long mNextFaviconLoadId;
+    private LruCache<String, Drawable> mFaviconsCache;
     private static final String USER_AGENT = GeckoApp.mAppContext.getDefaultUAString();
     private AndroidHttpClient mHttpClient;
 
     public interface OnFaviconLoadedListener {
         public void onFaviconLoaded(String url, Drawable favicon);
     }
 
     private class DatabaseHelper extends SQLiteOpenHelper {
@@ -134,49 +137,91 @@ public class Favicons {
     public Favicons(Context context) {
         Log.d(LOGTAG, "Creating Favicons instance");
 
         mContext = context;
         mDbHelper = new DatabaseHelper(context);
 
         mLoadTasks = Collections.synchronizedMap(new HashMap<Long,LoadFaviconTask>());
         mNextFaviconLoadId = 0;
+
+        // Create a favicon memory cache that have up to 1mb of size
+        mFaviconsCache = new LruCache<String, Drawable>(1024 * 1024) {
+            @Override
+            protected int sizeOf(String url, Drawable image) {
+                Bitmap bitmap = ((BitmapDrawable) image).getBitmap();
+                return bitmap.getRowBytes() * bitmap.getHeight();
+            }
+        };
     }
 
     private synchronized AndroidHttpClient getHttpClient() {
         if (mHttpClient != null)
             return mHttpClient;
 
         mHttpClient = AndroidHttpClient.newInstance(USER_AGENT);
         return mHttpClient;
     }
 
+    private void dispatchResult(final String pageUrl, final Drawable image,
+            final OnFaviconLoadedListener listener) {
+        if (pageUrl != null && image != null)
+            putFaviconInMemCache(pageUrl, image);
+
+        // We want to always run the listener on UI thread
+        GeckoAppShell.getMainHandler().post(new Runnable() {
+            public void run() {
+                if (listener != null)
+                    listener.onFaviconLoaded(pageUrl, image);
+            }
+        });
+    }
+
     public String getFaviconUrlForPageUrl(String pageUrl) {
         return mDbHelper.getFaviconUrlForPageUrl(pageUrl);
     }
 
     public long loadFavicon(String pageUrl, String faviconUrl, boolean persist,
             OnFaviconLoadedListener listener) {
 
         // Handle the case where page url is empty
         if (pageUrl == null || pageUrl.length() == 0) {
-            if (listener != null)
-                listener.onFaviconLoaded(null, null);
+            dispatchResult(null, null, listener);
+            return -1;
+        }
+
+        // Check if favicon is mem cached
+        Drawable image = getFaviconFromMemCache(pageUrl);
+        if (image != null) {
+            dispatchResult(pageUrl, image, listener);
+            return -1;
         }
 
         LoadFaviconTask task = new LoadFaviconTask(pageUrl, faviconUrl, persist, listener);
 
         long taskId = task.getId();
         mLoadTasks.put(taskId, task);
 
         task.execute();
 
         return taskId;
     }
 
+    public Drawable getFaviconFromMemCache(String pageUrl) {
+        return mFaviconsCache.get(pageUrl);
+    }
+
+    public void putFaviconInMemCache(String pageUrl, Drawable image) {
+        mFaviconsCache.put(pageUrl, image);
+    }
+
+    public void clearMemCache() {
+        mFaviconsCache.evictAll();
+    }
+
     public boolean cancelFaviconLoad(long taskId) {
         Log.d(LOGTAG, "Requesting cancelation of favicon load (" + taskId + ")");
 
         boolean cancelled = false;
         synchronized (mLoadTasks) {
             if (!mLoadTasks.containsKey(taskId))
                 return false;
 
@@ -250,17 +295,17 @@ public class Favicons {
 
                 mDbHelper.setFaviconUrlForPageUrl(mPageUrl, mFaviconUrl);
             }
         }
 
         // Runs in background thread
         private BitmapDrawable downloadFavicon(URL faviconUrl) {
             if (mFaviconUrl.startsWith("jar:jar:")) {
-                return GeckoJarReader.getBitmapDrawable(GeckoApp.mAppContext.getResources(), mFaviconUrl);
+                return GeckoJarReader.getBitmapDrawable(mContext.getResources(), mFaviconUrl);
             }
 
             URI uri;
             try {
                 uri = faviconUrl.toURI();
             } catch (URISyntaxException e) {
                 Log.d(LOGTAG, "Could not get URI for favicon");
                 return null;
@@ -334,25 +379,17 @@ public class Favicons {
             }
 
             return image;
         }
 
         @Override
         protected void onPostExecute(final BitmapDrawable image) {
             mLoadTasks.remove(mId);
-
-            if (mListener != null) {
-                // We want to always run the listener on UI thread
-                GeckoApp.mAppContext.runOnUiThread(new Runnable() {
-                    public void run() {
-                        mListener.onFaviconLoaded(mPageUrl, image);
-                    }
-                });
-            }
+            dispatchResult(mPageUrl, image, mListener);
         }
 
         @Override
         protected void onCancelled() {
             mLoadTasks.remove(mId);
 
             // Note that we don't call the listener callback if the
             // favicon load is cancelled.
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.GfxInfoThread;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
+import org.mozilla.gecko.gfx.InputConnectionHandler;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.TouchEventHandler;
 import org.mozilla.gecko.util.EventDispatcher;
 import org.mozilla.gecko.util.GeckoBackgroundThread;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -104,17 +105,17 @@ public class GeckoAppShell
     // static members only
     private GeckoAppShell() { }
 
     static private LinkedList<GeckoEvent> gPendingEvents =
         new LinkedList<GeckoEvent>();
 
     static private boolean gRestartScheduled = false;
 
-    static private GeckoInputConnection mInputConnection = null;
+    static private GeckoEditableListener mEditableListener = null;
 
     static private final HashMap<Integer, AlertNotification>
         mAlertNotifications = new HashMap<Integer, AlertNotification>();
 
     /* Keep in sync with constants found here:
       http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
     */
     static public final int WPL_STATE_START = 0x00000001;
@@ -529,18 +530,21 @@ public class GeckoAppShell
 
         // and go
         GeckoAppShell.nativeRun(combinedArgs);
     }
 
     // Called on the UI thread after Gecko loads.
     private static void geckoLoaded() {
         LayerView v = GeckoApp.mAppContext.getLayerView();
-        mInputConnection = GeckoInputConnection.create(v);
-        v.setInputConnectionHandler(mInputConnection);
+        GeckoEditable editable = new GeckoEditable();
+        InputConnectionHandler ich = GeckoInputConnection.create(v, editable);
+        v.setInputConnectionHandler(ich);
+        // install the gecko => editable listener
+        mEditableListener = editable;
     }
 
     static void sendPendingEventsToGecko() {
         try {
             while (!gPendingEvents.isEmpty()) {
                 GeckoEvent e = gPendingEvents.removeFirst();
                 notifyGeckoOfEvent(e);
             }
@@ -563,31 +567,40 @@ public class GeckoAppShell
 
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
     public static void notifyIME(int type, int state) {
-        if (mInputConnection != null)
-            mInputConnection.notifyIME(type, state);
+        if (mEditableListener != null) {
+            mEditableListener.notifyIME(type, state);
+        }
     }
 
-    public static void notifyIMEEnabled(int state, String typeHint, String modeHint,
-                                        String actionHint, boolean landscapeFS) {
-        // notifyIMEEnabled() still needs the landscapeFS parameter because it is called from JNI
-        // code that assumes it has the same signature as XUL Fennec's (which does use landscapeFS).
-        if (mInputConnection != null)
-            mInputConnection.notifyIMEEnabled(state, typeHint, modeHint, actionHint);
+    public static void notifyIMEEnabled(int state, String typeHint,
+                                        String modeHint, String actionHint,
+                                        boolean landscapeFS) {
+        // notifyIMEEnabled() still needs the landscapeFS parameter
+        // because it is called from JNI code that assumes it has the
+        // same signature as XUL Fennec's (which does use landscapeFS).
+        // Bug 807124 will eliminate the need for landscapeFS
+        if (mEditableListener != null) {
+            mEditableListener.notifyIMEEnabled(state, typeHint,
+                                               modeHint, actionHint);
+        }
     }
 
     public static void notifyIMEChange(String text, int start, int end, int newEnd) {
-        if (mInputConnection != null)
-            mInputConnection.notifyIMEChange(text, start, end, newEnd);
+        if (newEnd < 0) { // Selection change
+            mEditableListener.onSelectionChange(start, end);
+        } else { // Text change
+            mEditableListener.onTextChange(text, start, end, newEnd);
+        }
     }
 
     private static CountDownLatch sGeckoPendingAcks = null;
 
     // Block the current thread until the Gecko event loop is caught up
     synchronized public static void geckoEventSync() {
         sGeckoPendingAcks = new CountDownLatch(1);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createSyncEvent());
@@ -1992,18 +2005,20 @@ public class GeckoAppShell
         SmsManager.getInstance().clearMessageList(aListId);
     }
 
     public static boolean isTablet() {
         return GeckoApp.mAppContext.isTablet();
     }
 
     public static void viewSizeChanged() {
-        if (mInputConnection != null && mInputConnection.isIMEEnabled()) {
-            sendEventToGecko(GeckoEvent.createBroadcastEvent("ScrollTo:FocusedInput", ""));
+        LayerView v = GeckoApp.mAppContext.getLayerView();
+        if (v != null && v.isIMEEnabled()) {
+            sendEventToGecko(GeckoEvent.createBroadcastEvent(
+                    "ScrollTo:FocusedInput", ""));
         }
     }
 
     public static double[] getCurrentNetworkInformation() {
         return GeckoNetworkManager.getInstance().getCurrentInformation();
     }
 
     public static void enableNetworkNotifications() {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoEditable.java
@@ -0,0 +1,815 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Selection;
+import android.text.style.UnderlineSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.BackgroundColorSpan;
+import android.util.Log;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Semaphore;
+
+// interface for the UI thread
+interface GeckoEditableClient {
+    void sendEvent(GeckoEvent event);
+    Editable getEditable();
+    void setUpdateGecko(boolean update);
+    void setListener(GeckoEditableListener listener);
+}
+
+/* interface for the Editable to listen to the Gecko thread
+   and also for the UI thread to listen to the Editable */
+interface GeckoEditableListener {
+    void notifyIME(int type, int state);
+    void notifyIMEEnabled(int state, String typeHint,
+                          String modeHint, String actionHint);
+    void onSelectionChange(int start, int end);
+    void onTextChange(String text, int start, int oldEnd, int newEnd);
+}
+
+/*
+   GeckoEditable implements only some functions of Editable
+   The field mText contains the actual underlying
+   SpannableStringBuilder/Editable that contains our text.
+*/
+final class GeckoEditable
+        implements InvocationHandler, Editable,
+                   GeckoEditableClient, GeckoEditableListener {
+
+    private static final boolean DEBUG = false;
+    private static final String LOGTAG = "GeckoEditable";
+    private static final int NOTIFY_IME_REPLY_EVENT = 1;
+
+    // Filters to implement Editable's filtering functionality
+    private InputFilter[] mFilters;
+
+    private final SpannableStringBuilder mText;
+    private final Editable mProxy;
+    private GeckoEditableListener mListener;
+    private final ActionQueue mActionQueue;
+
+    private int mSavedSelectionStart;
+    private volatile int mGeckoUpdateSeqno;
+    private int mUIUpdateSeqno;
+    private int mLastUIUpdateSeqno;
+    private boolean mUpdateGecko;
+
+    /* An action that alters the Editable
+
+       Each action corresponds to a Gecko event. While the Gecko event is being sent to the Gecko
+       thread, the action stays on top of mActions queue. After the Gecko event is processed and
+       replied, the action is removed from the queue
+    */
+    private static final class Action {
+        // For input events (keypress, etc.); use with IME_SYNCHRONIZE
+        static final int TYPE_EVENT = 0;
+        // For Editable.replace() call; use with IME_REPLACE_TEXT
+        static final int TYPE_REPLACE_TEXT = 1;
+        /* For Editable.setSpan(Selection...) call; use with IME_SYNCHRONIZE
+           Note that we don't use this with IME_SET_SELECTION because we don't want to update the
+           Gecko selection at the point of this action. The Gecko selection is updated only after
+           UI has updated its selection (during IME_SYNCHRONIZE reply) */
+        static final int TYPE_SET_SELECTION = 2;
+        // For Editable.setSpan() call; use with IME_SYNCHRONIZE
+        static final int TYPE_SET_SPAN = 3;
+        // For Editable.removeSpan() call; use with IME_SYNCHRONIZE
+        static final int TYPE_REMOVE_SPAN = 4;
+
+        final int mType;
+        int mStart;
+        int mEnd;
+        CharSequence mSequence;
+        Object mSpanObject;
+        int mSpanFlags;
+        boolean mShouldUpdate;
+
+        Action(int type) {
+            mType = type;
+        }
+
+        static Action newReplaceText(CharSequence text, int start, int end) {
+            if (start < 0 || start > end) {
+                throw new IllegalArgumentException("invalid replace text offsets");
+            }
+            final Action action = new Action(TYPE_REPLACE_TEXT);
+            action.mSequence = text;
+            action.mStart = start;
+            action.mEnd = end;
+            return action;
+        }
+
+        static Action newSetSelection(int start, int end) {
+            if (start < 0 || start > end) {
+                throw new IllegalArgumentException("invalid selection offsets");
+            }
+            final Action action = new Action(TYPE_SET_SELECTION);
+            action.mStart = start;
+            action.mEnd = end;
+            return action;
+        }
+
+        static Action newSetSpan(Object object, int start, int end, int flags) {
+            if (start < 0 || start > end) {
+                throw new IllegalArgumentException("invalid span offsets");
+            }
+            final Action action = new Action(TYPE_SET_SPAN);
+            action.mSpanObject = object;
+            action.mStart = start;
+            action.mEnd = end;
+            action.mSpanFlags = flags;
+            return action;
+        }
+    }
+
+    /* Queue of editing actions sent to Gecko thread that
+       the Gecko thread has not responded to yet */
+    private final class ActionQueue {
+        private final ConcurrentLinkedQueue<Action> mActions;
+        private final Semaphore mActionsActive;
+
+        ActionQueue() {
+            mActions = new ConcurrentLinkedQueue<Action>();
+            mActionsActive = new Semaphore(1);
+        }
+
+        void offer(Action action) {
+            if (DEBUG) {
+                GeckoApp.assertOnUiThread();
+            }
+            /* Events don't need update because they generate text/selection
+               notifications which will do the updating for us */
+            if (action.mType != Action.TYPE_EVENT) {
+                action.mShouldUpdate = mUpdateGecko;
+            }
+            if (mActions.isEmpty()) {
+                mActionsActive.acquireUninterruptibly();
+                mActions.offer(action);
+            } else synchronized(this) {
+                // tryAcquire here in case Gecko thread has just released it
+                mActionsActive.tryAcquire();
+                mActions.offer(action);
+            }
+            switch (action.mType) {
+            case Action.TYPE_EVENT:
+            case Action.TYPE_SET_SELECTION:
+            case Action.TYPE_SET_SPAN:
+            case Action.TYPE_REMOVE_SPAN:
+                GeckoAppShell.sendEventToGecko(
+                        GeckoEvent.createIMEEvent(GeckoEvent.IME_SYNCHRONIZE));
+                break;
+            case Action.TYPE_REPLACE_TEXT:
+                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEReplaceEvent(
+                        action.mStart, action.mEnd, action.mSequence.toString()));
+                break;
+            }
+            ++mUIUpdateSeqno;
+        }
+
+        void poll() {
+            if (DEBUG) {
+                GeckoApp.assertOnGeckoThread();
+            }
+            if (mActions.isEmpty()) {
+                throw new IllegalStateException("empty actions queue");
+            }
+            mActions.poll();
+            // Don't bother locking if queue is not empty yet
+            if (mActions.isEmpty()) {
+                synchronized(this) {
+                    if (mActions.isEmpty()) {
+                        mActionsActive.release();
+                    }
+                }
+            }
+        }
+
+        Action peek() {
+            if (DEBUG) {
+                GeckoApp.assertOnGeckoThread();
+            }
+            if (mActions.isEmpty()) {
+                throw new IllegalStateException("empty actions queue");
+            }
+            return mActions.peek();
+        }
+
+        void syncWithGecko() {
+            if (DEBUG) {
+                GeckoApp.assertOnUiThread();
+            }
+            if (!mActions.isEmpty()) {
+                mActionsActive.acquireUninterruptibly();
+                mActionsActive.release();
+            }
+        }
+
+        boolean isEmpty() {
+            return mActions.isEmpty();
+        }
+    }
+
+    GeckoEditable() {
+        mActionQueue = new ActionQueue();
+        mSavedSelectionStart = -1;
+        mUpdateGecko = true;
+
+        mText = new SpannableStringBuilder();
+
+        final Class[] PROXY_INTERFACES = { Editable.class };
+        mProxy = (Editable)Proxy.newProxyInstance(
+                Editable.class.getClassLoader(),
+                PROXY_INTERFACES, this);
+    }
+
+    private static void geckoPostToUI(Runnable runnable) {
+        GeckoApp.mAppContext.mMainHandler.post(runnable);
+    }
+
+    private void geckoUpdateGecko(final boolean force) {
+        /* We do not increment the seqno here, but only check it, because geckoUpdateGecko is a
+           request for update. If we incremented the seqno here, geckoUpdateGecko would have
+           prevented other updates from occurring */
+        final int seqnoWhenPosted = mGeckoUpdateSeqno;
+
+        geckoPostToUI(new Runnable() {
+            public void run() {
+                mActionQueue.syncWithGecko();
+                if (seqnoWhenPosted == mGeckoUpdateSeqno) {
+                    uiUpdateGecko(force);
+                }
+            }
+        });
+    }
+
+    private void uiUpdateGecko(boolean force) {
+
+        if (!force && mUIUpdateSeqno == mLastUIUpdateSeqno) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "uiUpdateGecko() skipped");
+            }
+            return;
+        }
+        mLastUIUpdateSeqno = mUIUpdateSeqno;
+        mActionQueue.syncWithGecko();
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "uiUpdateGecko()");
+        }
+
+        final int selStart = mText.getSpanStart(Selection.SELECTION_START);
+        final int selEnd = mText.getSpanEnd(Selection.SELECTION_END);
+        int composingStart = mText.length();
+        int composingEnd = 0;
+        Object[] spans = mText.getSpans(0, composingStart, Object.class);
+
+        for (Object span : spans) {
+            if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
+                composingStart = Math.min(composingStart, mText.getSpanStart(span));
+                composingEnd = Math.max(composingEnd, mText.getSpanEnd(span));
+            }
+        }
+        if (DEBUG) {
+            Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd);
+            Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd);
+        }
+        if (composingStart >= composingEnd) {
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(
+                    GeckoEvent.IME_REMOVE_COMPOSITION));
+            if (selStart >= 0 && selEnd >= 0) {
+                GeckoAppShell.sendEventToGecko(
+                        GeckoEvent.createIMESelectEvent(selStart, selEnd));
+            }
+            return;
+        }
+
+        if (selEnd >= composingStart && selEnd <= composingEnd) {
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMERangeEvent(
+                    selEnd - composingStart, selEnd - composingStart,
+                    GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0));
+        }
+        int rangeStart = composingStart;
+        do {
+            int rangeType, rangeStyles = 0;
+            int rangeForeColor = 0, rangeBackColor = 0;
+            int rangeEnd = mText.nextSpanTransition(rangeStart, composingEnd, Object.class);
+
+            if (selStart > rangeStart && selStart < rangeEnd) {
+                rangeEnd = selStart;
+            } else if (selEnd > rangeStart && selEnd < rangeEnd) {
+                rangeEnd = selEnd;
+            }
+            spans = mText.getSpans(rangeStart, rangeEnd, Object.class);
+
+            if (DEBUG) {
+                Log.d(LOGTAG, " found " + spans.length + " spans @ " +
+                              rangeStart + "-" + rangeEnd);
+            }
+
+            if (spans.length == 0) {
+                rangeType = (selStart == rangeStart && selEnd == rangeEnd)
+                            ? GeckoEvent.IME_RANGE_SELECTEDRAWTEXT
+                            : GeckoEvent.IME_RANGE_RAWINPUT;
+            } else {
+                rangeType = (selStart == rangeStart && selEnd == rangeEnd)
+                            ? GeckoEvent.IME_RANGE_SELECTEDCONVERTEDTEXT
+                            : GeckoEvent.IME_RANGE_CONVERTEDTEXT;
+                for (Object span : spans) {
+                    if (span instanceof UnderlineSpan) {
+                        rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
+                    } else if (span instanceof ForegroundColorSpan) {
+                        rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
+                        rangeForeColor =
+                            ((ForegroundColorSpan)span).getForegroundColor();
+                    } else if (span instanceof BackgroundColorSpan) {
+                        rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
+                        rangeBackColor =
+                            ((BackgroundColorSpan)span).getBackgroundColor();
+                    }
+                }
+            }
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMERangeEvent(
+                    rangeStart - composingStart, rangeEnd - composingStart,
+                    rangeType, rangeStyles, rangeForeColor, rangeBackColor));
+            rangeStart = rangeEnd;
+
+            if (DEBUG) {
+                Log.d(LOGTAG, " added " + rangeType + " : " + rangeStyles +
+                              " : " + Integer.toHexString(rangeForeColor) +
+                              " : " + Integer.toHexString(rangeBackColor));
+            }
+        } while (rangeStart < composingEnd);
+
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createIMECompositionEvent(
+                composingStart, composingEnd));
+    }
+
+    // GeckoEditableClient interface
+
+    @Override
+    public void sendEvent(GeckoEvent event) {
+        if (DEBUG) {
+            // GeckoEditableClient methods should all be called from the UI thread
+            GeckoApp.assertOnUiThread();
+        }
+        /*
+           We are actually sending two events to Gecko here,
+           1. Event from the event parameter (key event, etc.)
+           2. Sync event from the mActionQueue.offer call
+           The first event is a normal GeckoEvent that does not reply back to us,
+           the second sync event will have a reply, during which we see that there is a pending
+           event-type action, and update the selection/composition/etc. accordingly.
+        */
+        GeckoAppShell.sendEventToGecko(event);
+        mActionQueue.offer(new Action(Action.TYPE_EVENT));
+    }
+
+    @Override
+    public Editable getEditable() {
+        if (DEBUG) {
+            // GeckoEditableClient methods should all be called from the UI thread
+            GeckoApp.assertOnUiThread();
+        }
+        return mProxy;
+    }
+
+    @Override
+    public void setUpdateGecko(boolean update) {
+        if (DEBUG) {
+            // GeckoEditableClient methods should all be called from the UI thread
+            GeckoApp.assertOnUiThread();
+        }
+        if (update) {
+            uiUpdateGecko(false);
+        }
+        mUpdateGecko = update;
+    }
+
+    @Override
+    public void setListener(GeckoEditableListener listener) {
+        if (DEBUG) {
+            // GeckoEditableClient methods should all be called from the UI thread
+            GeckoApp.assertOnUiThread();
+        }
+        mListener = listener;
+    }
+
+    // GeckoEditableListener interface
+
+    void geckoActionReply() {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            GeckoApp.assertOnGeckoThread();
+        }
+        final Action action = mActionQueue.peek();
+
+        switch (action.mType) {
+        case Action.TYPE_SET_SELECTION:
+            final int len = mText.length();
+            final int selStart = Math.min(action.mStart, len);
+            final int selEnd = Math.min(action.mEnd, len);
+
+            if (selStart < action.mStart || selEnd < action.mEnd) {
+                Log.w(LOGTAG, "IME sync error: selection out of bounds");
+            }
+            Selection.setSelection(mText, selStart, selEnd);
+            geckoPostToUI(new Runnable() {
+                public void run() {
+                    mActionQueue.syncWithGecko();
+                    final int start = Selection.getSelectionStart(mText);
+                    final int end = Selection.getSelectionEnd(mText);
+                    if (mListener != null &&
+                            selStart == start && selEnd == end) {
+                        // There has not been another new selection in the mean time that
+                        // made this notification out-of-date
+                        mListener.onSelectionChange(start, end);
+                    }
+                }
+            });
+            break;
+        case Action.TYPE_SET_SPAN:
+            mText.setSpan(action.mSpanObject, action.mStart, action.mEnd, action.mSpanFlags);
+            break;
+        }
+        if (action.mShouldUpdate) {
+            geckoUpdateGecko(false);
+        }
+        mActionQueue.poll();
+    }
+
+    @Override
+    public void notifyIME(final int type, final int state) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            GeckoApp.assertOnGeckoThread();
+        }
+        if (type == NOTIFY_IME_REPLY_EVENT) {
+            geckoActionReply();
+            return;
+        }
+        geckoPostToUI(new Runnable() {
+            public void run() {
+                // Make sure there are no other things going on
+                mActionQueue.syncWithGecko();
+                if (mListener != null) {
+                    mListener.notifyIME(type, state);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void notifyIMEEnabled(final int state, final String typeHint,
+                          final String modeHint, final String actionHint) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            GeckoApp.assertOnGeckoThread();
+        }
+        geckoPostToUI(new Runnable() {
+            public void run() {
+                // Make sure there are no other things going on
+                mActionQueue.syncWithGecko();
+                if (mListener != null) {
+                    mListener.notifyIMEEnabled(state, typeHint,
+                                               modeHint, actionHint);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSelectionChange(final int start, final int end) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            GeckoApp.assertOnGeckoThread();
+        }
+        if (start < 0 || start > end || end > mText.length()) {
+            throw new IllegalArgumentException("invalid selection notification range");
+        }
+        final int seqnoWhenPosted = ++mGeckoUpdateSeqno;
+
+        geckoPostToUI(new Runnable() {
+            public void run() {
+                mActionQueue.syncWithGecko();
+                /* check to see there has not been another action that potentially changed the
+                   selection. If so, we can skip this update because we know there is another
+                   update right after this one that will replace the effect of this update */
+                if (mGeckoUpdateSeqno == seqnoWhenPosted) {
+                    /* In this case, Gecko's selection has changed and it's notifying us to change
+                       Java's selection. In the normal case, whenever Java's selection changes,
+                       we go back and set Gecko's selection as well. However, in this case,
+                       since Gecko's selection is already up-to-date, we skip this step. */
+                    boolean oldUpdateGecko = mUpdateGecko;
+                    mUpdateGecko = false;
+                    Selection.setSelection(mProxy, start, end);
+                    mUpdateGecko = oldUpdateGecko;
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onTextChange(final String text, final int start,
+                      final int unboundedOldEnd, final int unboundedNewEnd) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            GeckoApp.assertOnGeckoThread();
+        }
+        if (start < 0 || start > unboundedOldEnd) {
+            throw new IllegalArgumentException("invalid text notification range");
+        }
+        /* For the "end" parameters, Gecko can pass in a large
+           number to denote "end of the text". Fix that here */
+        final int oldEnd = unboundedOldEnd > mText.length() ? mText.length() : unboundedOldEnd;
+        // new end should always match text
+        if (unboundedNewEnd < (start + text.length())) {
+            throw new IllegalArgumentException("newEnd does not match text");
+        }
+        final int newEnd = start + text.length();
+
+        if (!mActionQueue.isEmpty()) {
+            final Action action = mActionQueue.peek();
+            if (action.mType == Action.TYPE_REPLACE_TEXT &&
+                    action.mStart == start &&
+                    text.equals(action.mSequence.toString())) {
+                // Replace using saved text to preserve spans
+                mText.replace(start, oldEnd, action.mSequence,
+                              0, action.mSequence.length());
+            } else {
+                mText.replace(start, oldEnd, text, 0, text.length());
+            }
+        } else {
+            mText.replace(start, oldEnd, text, 0, text.length());
+            geckoPostToUI(new Runnable() {
+                public void run() {
+                    if (mListener != null) {
+                        mListener.onTextChange(text, start, oldEnd, newEnd);
+                    }
+                }
+            });
+        }
+    }
+
+    // InvocationHandler interface
+
+    private static StringBuilder debugAppend(StringBuilder sb, Object obj) {
+        if (obj == null) {
+            sb.append("null");
+        } else if (obj instanceof GeckoEditable) {
+            sb.append("GeckoEditable");
+        } else if (Proxy.isProxyClass(obj.getClass())) {
+            debugAppend(sb, Proxy.getInvocationHandler(obj));
+        } else if (obj instanceof CharSequence) {
+            sb.append("\"").append(obj.toString().replace('\n', '\u21b2')).append("\"");
+        } else if (obj.getClass().isArray()) {
+            Class cls = obj.getClass();
+            sb.append(cls.getComponentType().getSimpleName()).append("[")
+              .append(java.lang.reflect.Array.getLength(obj)).append("]");
+        } else {
+            sb.append(obj.toString());
+        }
+        return sb;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args)
+                         throws Throwable {
+        Object target;
+        final Class methodInterface = method.getDeclaringClass();
+        if (DEBUG) {
+            // Editable methods should all be called from the UI thread
+            GeckoApp.assertOnUiThread();
+        }
+        if (methodInterface == Editable.class ||
+                methodInterface == Appendable.class ||
+                methodInterface == Spannable.class) {
+            // Method alters the Editable; route calls to our implementation
+            target = this;
+        } else {
+            // Method queries the Editable; must sync with Gecko first
+            // then call on the inner Editable itself
+            mActionQueue.syncWithGecko();
+            target = mText;
+        }
+        Object ret = method.invoke(target, args);
+        if (DEBUG) {
+            StringBuilder log = new StringBuilder(method.getName());
+            log.append("(");
+            for (Object arg : args) {
+                debugAppend(log, arg).append(", ");
+            }
+            if (args.length > 0) {
+                log.setLength(log.length() - 2);
+            }
+            if (method.getReturnType().equals(Void.TYPE)) {
+                log.append(")");
+            } else {
+                debugAppend(log.append(") = "), ret);
+            }
+            Log.d(LOGTAG, log.toString());
+        }
+        return ret;
+    }
+
+    // Spannable interface
+
+    private static boolean isCompositionSpan(Object what, int flags) {
+        return (flags & Spanned.SPAN_COMPOSING) != 0 ||
+                what instanceof UnderlineSpan ||
+                what instanceof ForegroundColorSpan ||
+                what instanceof BackgroundColorSpan;
+    }
+
+    @Override
+    public void removeSpan(Object what) {
+        if (what == Selection.SELECTION_START ||
+                what == Selection.SELECTION_END) {
+            Log.w(LOGTAG, "selection removed with removeSpan()");
+        }
+        // Okay to remove immediately
+        mText.removeSpan(what);
+        if (mUpdateGecko) {
+            mActionQueue.offer(new Action(Action.TYPE_REMOVE_SPAN));
+        }
+    }
+
+    @Override
+    public void setSpan(Object what, int start, int end, int flags) {
+        if (what == Selection.SELECTION_START) {
+            if ((flags & Spanned.SPAN_INTERMEDIATE) != 0) {
+                // We will get the end offset next, just save the start for now
+                mSavedSelectionStart = start;
+            } else {
+                mActionQueue.offer(Action.newSetSelection(start, -1));
+            }
+        } else if (what == Selection.SELECTION_END) {
+            mActionQueue.offer(Action.newSetSelection(mSavedSelectionStart, end));
+            mSavedSelectionStart = -1;
+        } else {
+            mActionQueue.offer(Action.newSetSpan(what, start, end, flags));
+        }
+    }
+
+    // Appendable interface
+
+    @Override
+    public Editable append(CharSequence text) {
+        return replace(length(), length(), text, 0, text.length());
+    }
+
+    @Override
+    public Editable append(CharSequence text, int start, int end) {
+        return replace(length(), length(), text, start, end);
+    }
+
+    @Override
+    public Editable append(char text) {
+        return replace(length(), length(), String.valueOf(text), 0, 1);
+    }
+
+    // Editable interface
+
+    @Override
+    public InputFilter[] getFilters() {
+        return mFilters;
+    }
+
+    @Override
+    public void setFilters(InputFilter[] filters) {
+        mFilters = filters;
+    }
+
+    @Override
+    public void clearSpans() {
+        /* XXX this clears the selection spans too,
+           but there is no way to clear the corresponding selection in Gecko */
+        Log.w(LOGTAG, "selection cleared with clearSpans()");
+        mText.clearSpans();
+    }
+
+    @Override
+    public Editable replace(int st, int en,
+            CharSequence source, int start, int end) {
+
+        CharSequence text = source;
+        if (start < 0 || start > end || end > text.length()) {
+            throw new IllegalArgumentException("invalid replace offsets");
+        }
+        if (start != 0 || end != text.length()) {
+            text = text.subSequence(start, end);
+        }
+        if (mFilters != null) {
+            // Filter text before sending the request to Gecko
+            for (int i = 0; i < mFilters.length; ++i) {
+                final CharSequence cs = mFilters[i].filter(
+                        text, 0, text.length(), mProxy, st, en);
+                if (cs != null) {
+                    text = cs;
+                }
+            }
+        }
+        if (text == source) {
+            // Always create a copy
+            text = new SpannableString(source);
+        }
+        mActionQueue.offer(Action.newReplaceText(text,
+                Math.min(st, en), Math.max(st, en)));
+        return mProxy;
+    }
+
+    @Override
+    public void clear() {
+        replace(0, length(), "", 0, 0);
+    }
+
+    @Override
+    public Editable delete(int st, int en) {
+        return replace(st, en, "", 0, 0);
+    }
+
+    @Override
+    public Editable insert(int where, CharSequence text,
+                                int start, int end) {
+        return replace(where, where, text, start, end);
+    }
+
+    @Override
+    public Editable insert(int where, CharSequence text) {
+        return replace(where, where, text, 0, text.length());
+    }
+
+    @Override
+    public Editable replace(int st, int en, CharSequence text) {
+        return replace(st, en, text, 0, text.length());
+    }
+
+    /* GetChars interface */
+
+    @Override
+    public void getChars(int start, int end, char[] dest, int destoff) {
+        throw new UnsupportedOperationException();
+    }
+
+    /* Spanned interface */
+
+    @Override
+    public int getSpanEnd(Object tag) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getSpanFlags(Object tag) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getSpanStart(Object tag) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T[] getSpans(int start, int end, Class<T> type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int nextSpanTransition(int start, int limit, Class type) {
+        throw new UnsupportedOperationException();
+    }
+
+    /* CharSequence interface */
+
+    @Override
+    public char charAt(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int length() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String toString() {
+        throw new UnsupportedOperationException();
+    }
+}
+
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -75,24 +75,22 @@ public class GeckoEvent {
      */
     private static final int DOM_KEY_LOCATION_STANDARD = 0;
     private static final int DOM_KEY_LOCATION_LEFT = 1;
     private static final int DOM_KEY_LOCATION_RIGHT = 2;
     private static final int DOM_KEY_LOCATION_NUMPAD = 3;
     private static final int DOM_KEY_LOCATION_MOBILE = 4;
     private static final int DOM_KEY_LOCATION_JOYSTICK = 5;
 
-    public static final int IME_COMPOSITION_END = 0;
-    public static final int IME_COMPOSITION_BEGIN = 1;
-    public static final int IME_SET_TEXT = 2;
-    public static final int IME_GET_TEXT = 3;
-    public static final int IME_DELETE_TEXT = 4;
-    public static final int IME_SET_SELECTION = 5;
-    public static final int IME_GET_SELECTION = 6;
-    public static final int IME_ADD_RANGE = 7;
+    public static final int IME_SYNCHRONIZE = 0;
+    public static final int IME_REPLACE_TEXT = 1;
+    public static final int IME_SET_SELECTION = 2;
+    public static final int IME_ADD_COMPOSITION_RANGE = 3;
+    public static final int IME_UPDATE_COMPOSITION = 4;
+    public static final int IME_REMOVE_COMPOSITION = 5;
 
     public static final int IME_RANGE_CARETPOSITION = 1;
     public static final int IME_RANGE_RAWINPUT = 2;
     public static final int IME_RANGE_SELECTEDRAWTEXT = 3;
     public static final int IME_RANGE_CONVERTEDTEXT = 4;
     public static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5;
 
     public static final int IME_RANGE_UNDERLINE = 1;
@@ -113,17 +111,18 @@ public class GeckoEvent {
     public float[] mPressures;
     public Point[] mPointRadii;
     public Rect mRect;
     public double mX, mY, mZ;
 
     public int mMetaState, mFlags;
     public int mKeyCode, mUnicodeChar;
     public int mRepeatCount;
-    public int mOffset, mCount;
+    public int mCount;
+    public int mStart, mEnd;
     public String mCharacters, mCharactersExtra;
     public int mRangeType, mRangeStyles;
     public int mRangeForeColor, mRangeBackColor;
     public Location mLocation;
     public Address  mAddress;
     public int mDomKeyLocation;
 
     public double mBandwidth;
@@ -454,54 +453,61 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createLocationEvent(Location l) {
         GeckoEvent event = new GeckoEvent(LOCATION_EVENT);
         event.mLocation = l;
         return event;
     }
 
-    public static GeckoEvent createIMEEvent(int imeAction, int offset, int count) {
+    public static GeckoEvent createIMEEvent(int action) {
         GeckoEvent event = new GeckoEvent(IME_EVENT);
-        event.mAction = imeAction;
-        event.mOffset = offset;
-        event.mCount = count;
+        event.mAction = action;
         return event;
     }
 
-    private void InitIMERange(int action, int offset, int count,
-                              int rangeType, int rangeStyles,
-                              int rangeForeColor, int rangeBackColor) {
-        mAction = action;
-        mOffset = offset;
-        mCount = count;
-        mRangeType = rangeType;
-        mRangeStyles = rangeStyles;
-        mRangeForeColor = rangeForeColor;
-        mRangeBackColor = rangeBackColor;
-        return;
-    }
-    
-    public static GeckoEvent createIMERangeEvent(int offset, int count,
-                                                 int rangeType, int rangeStyles,
-                                                 int rangeForeColor, int rangeBackColor,
-                                                 String text) {
+    public static GeckoEvent createIMEReplaceEvent(int start, int end,
+                                                   String text) {
         GeckoEvent event = new GeckoEvent(IME_EVENT);
-        event.InitIMERange(IME_SET_TEXT, offset, count, rangeType, rangeStyles,
-                           rangeForeColor, rangeBackColor);
+        event.mAction = IME_REPLACE_TEXT;
+        event.mStart = start;
+        event.mEnd = end;
         event.mCharacters = text;
         return event;
     }
 
-    public static GeckoEvent createIMERangeEvent(int offset, int count,
-                                                 int rangeType, int rangeStyles,
-                                                 int rangeForeColor, int rangeBackColor) {
+    public static GeckoEvent createIMESelectEvent(int start, int end) {
+        GeckoEvent event = new GeckoEvent(IME_EVENT);
+        event.mAction = IME_SET_SELECTION;
+        event.mStart = start;
+        event.mEnd = end;
+        return event;
+    }
+
+    public static GeckoEvent createIMECompositionEvent(int start, int end) {
         GeckoEvent event = new GeckoEvent(IME_EVENT);
-        event.InitIMERange(IME_ADD_RANGE, offset, count, rangeType, rangeStyles,
-                           rangeForeColor, rangeBackColor);
+        event.mAction = IME_UPDATE_COMPOSITION;
+        event.mStart = start;
+        event.mEnd = end;
+        return event;
+    }
+
+    public static GeckoEvent createIMERangeEvent(int start,
+                                                 int end, int rangeType,
+                                                 int rangeStyles,
+                                                 int rangeForeColor,
+                                                 int rangeBackColor) {
+        GeckoEvent event = new GeckoEvent(IME_EVENT);
+        event.mAction = IME_ADD_COMPOSITION_RANGE;
+        event.mStart = start;
+        event.mEnd = end;
+        event.mRangeType = rangeType;
+        event.mRangeStyles = rangeStyles;
+        event.mRangeForeColor = rangeForeColor;
+        event.mRangeBackColor = rangeBackColor;
         return event;
     }
 
     public static GeckoEvent createDrawEvent(Rect rect) {
         GeckoEvent event = new GeckoEvent(DRAW);
         event.mRect = rect;
         return event;
     }
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -5,802 +5,250 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 
 import android.R;
 import android.content.Context;
 import android.os.Build;
-import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.text.method.KeyListener;
 import android.text.method.TextKeyListener;
-import android.text.style.BackgroundColorSpan;
-import android.text.style.CharacterStyle;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.UnderlineSpan;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.LogPrinter;
-import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.Timer;
 import java.util.TimerTask;
 
 class GeckoInputConnection
     extends BaseInputConnection
-    implements TextWatcher, InputConnectionHandler {
+    implements InputConnectionHandler, GeckoEditableListener {
 
     private static final boolean DEBUG = false;
     protected static final String LOGTAG = "GeckoInputConnection";
 
     // IME stuff
     public static final int IME_STATE_DISABLED = 0;
     public static final int IME_STATE_ENABLED = 1;
     public static final int IME_STATE_PASSWORD = 2;
     public static final int IME_STATE_PLUGIN = 3;
 
     private static final int NOTIFY_IME_RESETINPUTSTATE = 0;
-    private static final int NOTIFY_IME_SETOPENSTATE = 1;
     private static final int NOTIFY_IME_CANCELCOMPOSITION = 2;
     private static final int NOTIFY_IME_FOCUSCHANGE = 3;
 
-    private static final int NO_COMPOSITION_STRING = -1;
-
     private static final int INLINE_IME_MIN_DISPLAY_SIZE = 480;
 
-    private static final char UNICODE_BULLET                    = '\u2022';
-    private static final char UNICODE_CENT_SIGN                 = '\u00a2';
-    private static final char UNICODE_COPYRIGHT_SIGN            = '\u00a9';
-    private static final char UNICODE_CRARR                     = '\u21b2'; // &crarr;
-    private static final char UNICODE_DIVISION_SIGN             = '\u00f7';
-    private static final char UNICODE_DOUBLE_LOW_QUOTATION_MARK = '\u201e';
-    private static final char UNICODE_ELLIPSIS                  = '\u2026';
-    private static final char UNICODE_EURO_SIGN                 = '\u20ac';
-    private static final char UNICODE_INVERTED_EXCLAMATION_MARK = '\u00a1';
-    private static final char UNICODE_MULTIPLICATION_SIGN       = '\u00d7';
-    private static final char UNICODE_PI                        = '\u03a0';
-    private static final char UNICODE_PILCROW_SIGN              = '\u00b6';
-    private static final char UNICODE_POUND_SIGN                = '\u00a3';
-    private static final char UNICODE_REGISTERED_SIGN           = '\u00ae';
-    private static final char UNICODE_SQUARE_ROOT               = '\u221a';
-    private static final char UNICODE_TRADEMARK_SIGN            = '\u2122';
-    private static final char UNICODE_WHITE_BULLET              = '\u25e6';
-    private static final char UNICODE_YEN_SIGN                  = '\u00a5';
+    private static final Timer mIMETimer = new Timer("GeckoInputConnection Timer");
 
-    private static final Timer mIMETimer = new Timer("GeckoInputConnection Timer");
     private static int mIMEState;
     private static String mIMETypeHint = "";
     private static String mIMEModeHint = "";
     private static String mIMEActionHint = "";
 
     private String mCurrentInputMethod;
 
-    // Is a composition active?
-    private int mCompositionStart = NO_COMPOSITION_STRING;
-    private boolean mCommittingText;
-    private KeyCharacterMap mKeyCharacterMap;
-    private final Editable mEditable;
+    private final GeckoEditableClient mEditableClient;
     protected int mBatchEditCount;
     private ExtractedTextRequest mUpdateRequest;
     private final ExtractedText mUpdateExtract = new ExtractedText();
 
-    public static GeckoInputConnection create(View targetView) {
+    public static InputConnectionHandler create(View targetView,
+                                                GeckoEditableClient editable) {
         if (DEBUG)
-            return new DebugGeckoInputConnection(targetView);
+            return DebugGeckoInputConnection.create(targetView, editable);
         else
-            return new GeckoInputConnection(targetView);
+            return new GeckoInputConnection(targetView, editable);
     }
 
-    protected GeckoInputConnection(View targetView) {
+    protected GeckoInputConnection(View targetView,
+                                   GeckoEditableClient editable) {
         super(targetView, true);
-        mEditable = Editable.Factory.getInstance().newEditable("");
-        spanAndSelectEditable();
+        mEditableClient = editable;
+        // install the editable => input connection listener
+        editable.setListener(this);
         mIMEState = IME_STATE_DISABLED;
     }
 
     @Override
-    public boolean beginBatchEdit() {
+    public synchronized boolean beginBatchEdit() {
         mBatchEditCount++;
+        mEditableClient.setUpdateGecko(false);
         return true;
     }
 
     @Override
-    public boolean endBatchEdit() {
+    public synchronized boolean endBatchEdit() {
         if (mBatchEditCount > 0) {
             mBatchEditCount--;
+            if (mBatchEditCount == 0) {
+                mEditableClient.setUpdateGecko(true);
+            }
         } else {
             Log.w(LOGTAG, "endBatchEdit() called, but mBatchEditCount == 0?!");
         }
         return true;
     }
 
     @Override
-    public boolean commitCompletion(CompletionInfo text) {
-        return commitText(text.getText(), 1);
-    }
-
-    @Override
-    public boolean commitText(CharSequence text, int newCursorPosition) {
-        if (mCommittingText)
-            Log.e(LOGTAG, "Please report this bug:",
-                  new IllegalStateException("commitText, but already committing text?!"));
-
-        mCommittingText = true;
-        replaceText(text, newCursorPosition, false);
-        mCommittingText = false;
-
-        if (hasCompositionString()) {
-            if (DEBUG) Log.d(LOGTAG, ". . . commitText: endComposition");
-            endComposition();
-        }
-        return true;
-    }
-
-    @Override
-    public boolean finishComposingText() {
-        // finishComposingText() is called by the input method manager from a background
-        // thread so we have to make sure it's run in the ui thread.
-        postToUiThread(new Runnable() {
-            public void run() {
-                if (hasCompositionString()) {
-                    if (DEBUG) Log.d(LOGTAG, ". . . finishComposingText: endComposition");
-                    endComposition();
-                }
-                final Editable content = getEditable();
-                if (content != null) {
-                    beginBatchEdit();
-                    removeComposingSpans(content);
-                    endBatchEdit();
-                }
-            }
-        });
-        return true;
-    }
-
-    @Override
     public Editable getEditable() {
-        return mEditable;
+        return mEditableClient.getEditable();
     }
 
     @Override
     public boolean performContextMenuAction(int id) {
-        String text = mEditable.toString();
-        Span selection = getSelection();
+        Editable editable = getEditable();
+        int selStart = Selection.getSelectionStart(editable);
+        int selEnd = Selection.getSelectionEnd(editable);
 
         switch (id) {
             case R.id.selectAll:
-                setSelection(0, text.length());
+                setSelection(0, editable.length());
                 break;
             case R.id.cut:
-                // Fill the clipboard
-                GeckoAppShell.setClipboardText(text);
                 // If selection is empty, we'll select everything
-                if (selection.length == 0)
-                    GeckoAppShell.sendEventToGecko(
-                        GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, 0, text.length()));
-                GeckoAppShell.sendEventToGecko(
-                    GeckoEvent.createIMEEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
+                if (selStart == selEnd) {
+                    // Fill the clipboard
+                    GeckoAppShell.setClipboardText(editable.toString());
+                    editable.clear();
+                } else {
+                    GeckoAppShell.setClipboardText(
+                            editable.toString().substring(
+                                Math.min(selStart, selEnd),
+                                Math.max(selStart, selEnd)));
+                    editable.delete(selStart, selEnd);
+                }
                 break;
             case R.id.paste:
                 commitText(GeckoAppShell.getClipboardText(), 1);
                 break;
             case R.id.copy:
                 // Copy the current selection or the empty string if nothing is selected.
-                String copiedText = selection.length > 0
-                                    ? text.substring(selection.start, selection.end)
-                                    : "";
-                GeckoAppShell.setClipboardText(text);
+                String copiedText = selStart == selEnd ? "" :
+                                    editable.toString().substring(
+                                        Math.min(selStart, selEnd),
+                                        Math.max(selStart, selEnd));
+                GeckoAppShell.setClipboardText(copiedText);
                 break;
         }
         return true;
     }
 
     @Override
     public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
         if (req == null)
             return null;
 
         if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0)
             mUpdateRequest = req;
 
-        Span selection = getSelection();
+        Editable editable = getEditable();
+        int selStart = Selection.getSelectionStart(editable);
+        int selEnd = Selection.getSelectionEnd(editable);
 
         ExtractedText extract = new ExtractedText();
         extract.flags = 0;
         extract.partialStartOffset = -1;
         extract.partialEndOffset = -1;
-        extract.selectionStart = selection.start;
-        extract.selectionEnd = selection.end;
+        extract.selectionStart = selStart;
+        extract.selectionEnd = selEnd;
         extract.startOffset = 0;
-        extract.text = mEditable.toString();
+        extract.text = editable;
 
         return extract;
     }
 
-    @Override
-    public boolean setSelection(int start, int end) {
-        // Some IMEs call setSelection() with negative or stale indexes, so clamp them.
-        Span newSelection = Span.clamp(start, end, mEditable);
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION,
-                                                                 newSelection.start,
-                                                                 newSelection.length));
-        return super.setSelection(newSelection.start, newSelection.end);
-    }
-
     private static void postToUiThread(Runnable runnable) {
         // postToUiThread() is called by the Gecko and TimerTask threads.
         // The UI thread does not need to post Runnables to itself.
         GeckoApp.mAppContext.mMainHandler.post(runnable);
     }
 
-    @Override
-    public CharSequence getTextBeforeCursor(int length, int flags) {
-        // Avoid underrunning text buffer.
-        Span selection = getSelection();
-        if (length > selection.start) {
-            length = selection.start;
-        }
-
-        if (length < 1) {
-            return "";
-        }
-
-        return super.getTextBeforeCursor(length, flags);
-    }
-
-    @Override
-    public CharSequence getTextAfterCursor(int length, int flags) {
-        // Avoid overrunning text buffer.
-        Span selection = getSelection();
-        int contentLength = mEditable.length();
-        if (selection.end + length > contentLength) {
-            length = contentLength - selection.end;
-        }
-
-        if (length < 1) {
-            return "";
-        }
-
-        return super.getTextAfterCursor(length, flags);
-    }
-
-    @Override
-    public boolean setComposingText(CharSequence text, int newCursorPosition) {
-        // setComposingText() places the given text into the editable, replacing any existing
-        // composing text. This method will likely be called multiple times while we are composing
-        // text.
-        return super.setComposingText(text, newCursorPosition);
-    }
-
     private static View getView() {
         return GeckoApp.mAppContext.getLayerView();
     }
 
-    private Span getSelection() {
-        int start = Selection.getSelectionStart(mEditable);
-        int end = Selection.getSelectionEnd(mEditable);
-        return Span.clamp(start, end, mEditable);
-    }
-
-    private void replaceText(CharSequence text, int newCursorPosition, boolean composing) {
-        if (DEBUG) {
-            Log.d(LOGTAG, String.format("IME: replaceText(\"%s\", %d, %b)",
-                                        text, newCursorPosition, composing));
-            GeckoApp.assertOnUiThread();
-        }
-
-        if (text == null)
-            text = "";
-
-        beginBatchEdit();
-
-        // delete composing text set previously.
-        int a;
-        int b;
-
-        Span composingSpan = getComposingSpan();
-        if (composingSpan != null) {
-            removeComposingSpans(mEditable);
-            a = composingSpan.start;
-            b = composingSpan.end;
-            composingSpan = null;
-        } else {
-            Span selection = getSelection();
-            a = selection.start;
-            b = selection.end;
-        }
-
-        if (composing) {
-            Spannable sp = null;
-            if (!(text instanceof Spannable)) {
-                sp = new SpannableStringBuilder(text);
-                text = sp;
-                // Underline the active composition string.
-                sp.setSpan(new UnderlineSpan(), 0, sp.length(),
-                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
-            } else {
-                sp = (Spannable) text;
-            }
-            setComposingSpans(sp);
-        }
-
-        if (DEBUG) Log.d(LOGTAG, "Replacing from " + a + " to " + b + " with \""
-                + text + "\", composing=" + composing
-                + ", type=" + text.getClass().getCanonicalName());
-
-        if (DEBUG) {
-            LogPrinter lp = new LogPrinter(Log.VERBOSE, LOGTAG);
-            lp.println("Current text:");
-            TextUtils.dumpSpans(mEditable, lp, "  ");
-            lp.println("Composing text:");
-            TextUtils.dumpSpans(text, lp, "  ");
-        }
-
-        // Position the cursor appropriately, so that after replacing the
-        // desired range of text it will be located in the correct spot.
-        // This allows us to deal with filters performing edits on the text
-        // we are providing here.
-        if (newCursorPosition > 0) {
-            newCursorPosition += b - 1;
-        } else {
-            newCursorPosition += a;
-        }
-        if (newCursorPosition < 0) newCursorPosition = 0;
-        if (newCursorPosition > mEditable.length())
-            newCursorPosition = mEditable.length();
-        Selection.setSelection(mEditable, newCursorPosition);
-
-        mEditable.replace(a, b, text);
-
-        if (DEBUG) {
-            LogPrinter lp = new LogPrinter(Log.VERBOSE, LOGTAG);
-            lp.println("Final text:");
-            TextUtils.dumpSpans(mEditable, lp, "  ");
-        }
-
-        endBatchEdit();
-    }
-
-    @Override
-    public boolean setComposingRegion(int start, int end) {
-        if (hasCompositionString()) {
-            if (DEBUG) Log.d(LOGTAG, ". . . setComposingRegion: endComposition");
-            endComposition();
-        }
-
-        Span newComposingRegion = Span.clamp(start, end, mEditable);
-        return super.setComposingRegion(newComposingRegion.start, newComposingRegion.end);
-    }
-
-    public String getComposingText() {
-        Span composingSpan = getComposingSpan();
-        if (composingSpan == null || composingSpan.length == 0) {
-            return "";
-        }
-
-        return TextUtils.substring(mEditable, composingSpan.start, composingSpan.end);
-    }
-
-    public boolean onKeyDel() {
-        // Some IMEs don't update us on deletions
-        // In that case we are not updated when a composition
-        // is destroyed, and Bad Things happen
-
-        if (!hasCompositionString())
-            return false;
-
-        String text = getComposingText();
-
-        if (text != null && text.length() > 1) {
-            text = text.substring(0, text.length() - 1);
-            replaceText(text, 1, false);
-            return false;
-        }
-
-        commitText(null, 1);
-        return true;
-    }
-
     private static InputMethodManager getInputMethodManager() {
         View view = getView();
         if (view == null) {
             return null;
         }
         Context context = view.getContext();
         return InputMethods.getInputMethodManager(context);
     }
 
-    protected void notifyTextChange(String text, int start, int oldEnd, int newEnd) {
-        if (mBatchEditCount == 0) {
-            if (!text.contentEquals(mEditable)) {
-                if (DEBUG) Log.d(LOGTAG, ". . . notifyTextChange: current mEditable="
-                                         + prettyPrintString(mEditable));
+    public void onTextChange(String text, int start, int oldEnd, int newEnd) {
 
-                // Editable will be updated by IME event
-                if (!hasCompositionString())
-                    setEditable(text);
-            }
+        if (mBatchEditCount > 0 || mUpdateRequest == null) {
+            return;
         }
 
-        if (mUpdateRequest == null)
+        final InputMethodManager imm = getInputMethodManager();
+        if (imm == null) {
             return;
-
-        InputMethodManager imm = getInputMethodManager();
-        if (imm == null)
-            return;
+        }
+        final View v = getView();
+        final Editable editable = getEditable();
 
         mUpdateExtract.flags = 0;
-
-        // We update from (0, oldEnd) to (0, newEnd) because some Android IMEs
+        // Update from (0, oldEnd) to (0, newEnd) because some IMEs
         // assume that updates start at zero, according to jchen.
         mUpdateExtract.partialStartOffset = 0;
-        mUpdateExtract.partialEndOffset = oldEnd;
-
-        String updatedText = (newEnd > text.length() ? text : text.substring(0, newEnd));
-        int updatedTextLength = updatedText.length();
+        mUpdateExtract.partialEndOffset = editable.length();
+        mUpdateExtract.selectionStart =
+                Selection.getSelectionStart(editable);
+        mUpdateExtract.selectionEnd =
+                Selection.getSelectionEnd(editable);
+        mUpdateExtract.startOffset = 0;
+        mUpdateExtract.text = editable;
 
-        // Faster to not query for selection
-        mUpdateExtract.selectionStart = updatedTextLength;
-        mUpdateExtract.selectionEnd = updatedTextLength;
-
-        mUpdateExtract.text = updatedText;
-        mUpdateExtract.startOffset = 0;
-
-        View v = getView();
-        imm.updateExtractedText(v, mUpdateRequest.token, mUpdateExtract);
+        imm.updateExtractedText(v, mUpdateRequest.token,
+                                mUpdateExtract);
     }
 
-    protected void notifySelectionChange(int start, int end) {
-        if (mBatchEditCount == 0) {
-            Span newSelection = Span.clamp(start, end, mEditable);
-            start = newSelection.start;
-            end = newSelection.end;
-
-            Span currentSelection = getSelection();
-            int a = currentSelection.start;
-            int b = currentSelection.end;
-
-            if (start != a || end != b) {
-                if (DEBUG) {
-                    Log.d(LOGTAG, String.format(
-                          ". . . notifySelectionChange: current editable selection: [%d, %d)",
-                          a, b));
-                }
-
-                super.setSelection(start, end);
+    public void onSelectionChange(int start, int end) {
 
-                // Check if the selection is inside composing span
-                Span composingSpan = getComposingSpan();
-                if (composingSpan != null) {
-                    int ca = composingSpan.start;
-                    int cb = composingSpan.end;
-                    if (start < ca || start > cb || end < ca || end > cb) {
-                        if (DEBUG) Log.d(LOGTAG, ". . . notifySelectionChange: removeComposingSpans");
-                        removeComposingSpans(mEditable);
-                    }
-                }
-            }
+        if (mBatchEditCount > 0) {
+            return;
         }
-
-        // FIXME: Remove this postToUiThread() after bug 780543 is fixed.
-        final int oldStart = start;
-        final int oldEnd = end;
-        postToUiThread(new Runnable() {
-            public void run() {
-                InputMethodManager imm = getInputMethodManager();
-                if (imm != null && imm.isFullscreenMode()) {
-                    int newStart;
-                    int newEnd;
-                    Span span = getComposingSpan();
-                    if (span != null && hasCompositionString()) {
-                        newStart = span.start;
-                        newEnd = span.end;
-                    } else {
-                        newStart = -1;
-                        newEnd = -1;
-                    }
-                    View v = getView();
-                    imm.updateSelection(v, oldStart, oldEnd, newStart, newEnd);
-                }
-            }
-        });
+        final InputMethodManager imm = getInputMethodManager();
+        if (imm == null) {
+            return;
+        }
+        final View v = getView();
+        final Editable editable = getEditable();
+        imm.updateSelection(v, start, end, getComposingSpanStart(editable),
+                            getComposingSpanEnd(editable));
     }
 
     protected void resetCompositionState() {
         if (mBatchEditCount > 0) {
             Log.d(LOGTAG, "resetCompositionState: resetting mBatchEditCount "
                           + mBatchEditCount + " -> 0");
             mBatchEditCount = 0;
         }
 
-        removeComposingSpans(mEditable);
-        mCompositionStart = NO_COMPOSITION_STRING;
+        removeComposingSpans(getEditable());
         mUpdateRequest = null;
     }
 
-    // TextWatcher
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        if (hasCompositionString() && mCompositionStart != start) {
-            // Changed range is different from the composition, need to reset the composition
-            endComposition();
-        }
-
-        CharSequence changedText = s.subSequence(start, start + count);
-        if (DEBUG) {
-            Log.d(LOGTAG, "onTextChanged: changedText=\"" + changedText + "\"");
-        }
-
-        if (changedText.length() == 1) {
-            char changedChar = changedText.charAt(0);
-
-            // Some IMEs (e.g. SwiftKey X) send a string with '\n' when Enter is pressed
-            // Such string cannot be handled by Gecko, so we convert it to a key press instead
-            if (changedChar == '\n') {
-                processKeyDown(KeyEvent.KEYCODE_ENTER, new KeyEvent(KeyEvent.ACTION_DOWN,
-                                                                    KeyEvent.KEYCODE_ENTER));
-                processKeyUp(KeyEvent.KEYCODE_ENTER, new KeyEvent(KeyEvent.ACTION_UP,
-                                                                  KeyEvent.KEYCODE_ENTER));
-                return;
-            }
-
-            // If we are committing a single character and didn't have an active composition string,
-            // we can send Gecko keydown/keyup events instead of composition events.
-            if (mCommittingText && !hasCompositionString() && sendKeyEventsToGecko(changedChar)) {
-                // Block this thread until all pending events are processed
-                GeckoAppShell.geckoEventSync();
-                return;
-            }
-        }
-
-        boolean startCompositionString = !hasCompositionString();
-        if (startCompositionString) {
-            if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_COMPOSITION_BEGIN");
-            GeckoAppShell.sendEventToGecko(
-                GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
-            mCompositionStart = start;
-
-            if (DEBUG) {
-                Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + start + ", len="
-                              + before);
-            }
-
-            GeckoAppShell.sendEventToGecko(
-                GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start, before));
-        }
-
-        sendTextToGecko(changedText, start + count);
-
-        if (DEBUG) {
-            Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + (start + count)
-                          + ", 0");
-        }
-
-        GeckoAppShell.sendEventToGecko(
-            GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0));
-
-        // End composition if all characters in the word have been deleted.
-        // This fixes autocomplete results not appearing.
-        if (count == 0 || (startCompositionString && mCommittingText))
-            endComposition();
-
-        // Block this thread until all pending events are processed
-        GeckoAppShell.geckoEventSync();
-    }
-
-    private boolean sendKeyEventsToGecko(char inputChar) {
-        // Synthesize VKB key events that could plausibly generate the input character.
-        KeyEvent[] events = synthesizeKeyEvents(inputChar);
-        if (events == null) {
-            if (DEBUG) {
-                Log.d(LOGTAG, "synthesizeKeyEvents: char '" + inputChar
-                              + "' has no virtual key mapping");
-            }
-            return false;
-        }
-
-        boolean sentKeyEvents = false;
-
-        for (KeyEvent event : events) {
-            if (!KeyEvent.isModifierKey(event.getKeyCode())) {
-                if (DEBUG) {
-                    Log.d(LOGTAG, "synthesizeKeyEvents: char '" + inputChar
-                                  + "' -> action=" + event.getAction()
-                                  + ", keyCode=" + event.getKeyCode()
-                                  + ", UnicodeChar='" + (char) event.getUnicodeChar() + "'");
-                }
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
-                sentKeyEvents = true;
-            }
-        }
-
-        return sentKeyEvents;
-    }
-
-    private KeyEvent[] synthesizeKeyEvents(char inputChar) {
-        // Some symbol characters produce unusual key events on Froyo and Gingerbread.
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
-            switch (inputChar) {
-                case '&':
-                    // Some Gingerbread devices' KeyCharacterMaps return ALT+7 instead of SHIFT+7,
-                    // but some devices like the Droid Bionic treat SHIFT+7 as '7'. So just return
-                    // null and onTextChanged() will send "&" as a composition string instead of
-                    // KEY_DOWN + KEY_UP event pair. This may break web content listening for '&'
-                    // key events, but they will still receive "&" input event.
-                    return null;
-
-                case '<':
-                case '>':
-                    // We can't synthesize KeyEvents for '<' or '>' because Froyo and Gingerbread
-                    // return incorrect shifted char codes from KeyEvent.getUnicodeChar().
-                    // Send these characters as composition strings, not key events.
-                    return null;
-
-                // Some symbol characters produce key events on Froyo and Gingerbread, but not
-                // Honeycomb and ICS. Send these characters as composition strings, not key events,
-                // to more closely mimic Honeycomb and ICS.
-                case UNICODE_BULLET:
-                case UNICODE_CENT_SIGN:
-                case UNICODE_COPYRIGHT_SIGN:
-                case UNICODE_DIVISION_SIGN:
-                case UNICODE_DOUBLE_LOW_QUOTATION_MARK:
-                case UNICODE_ELLIPSIS:
-                case UNICODE_EURO_SIGN:
-                case UNICODE_INVERTED_EXCLAMATION_MARK:
-                case UNICODE_MULTIPLICATION_SIGN:
-                case UNICODE_PI:
-                case UNICODE_PILCROW_SIGN:
-                case UNICODE_POUND_SIGN:
-                case UNICODE_REGISTERED_SIGN:
-                case UNICODE_SQUARE_ROOT:
-                case UNICODE_TRADEMARK_SIGN:
-                case UNICODE_WHITE_BULLET:
-                case UNICODE_YEN_SIGN:
-                    return null;
-
-                default:
-                    // Look up the character's key events in KeyCharacterMap below.
-                    break;
-            }
-        }
-
-        if (mKeyCharacterMap == null) {
-            mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-        }
-
-        char[] inputChars = { inputChar };
-        return mKeyCharacterMap.getEvents(inputChars);
-    }
-
-    private static KeyEvent[] createKeyDownKeyUpEvents(int keyCode, int metaState) {
-        long now = SystemClock.uptimeMillis();
-        KeyEvent keyDown = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, metaState);
-        KeyEvent keyUp = KeyEvent.changeAction(keyDown, KeyEvent.ACTION_UP);
-        KeyEvent[] events = { keyDown, keyUp };
-        return events;
-    }
-
-    private void endComposition() {
-        if (DEBUG) {
-            Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END");
-            GeckoApp.assertOnUiThread();
-        }
-
-        if (!hasCompositionString())
-            Log.e(LOGTAG, "Please report this bug:",
-                  new IllegalStateException("endComposition, but not composing text?!"));
-
-        GeckoAppShell.sendEventToGecko(
-            GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
-
-        mCompositionStart = NO_COMPOSITION_STRING;
-    }
-
-    private void sendTextToGecko(CharSequence text, int caretPos) {
-        if (DEBUG) {
-            Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")");
-            GeckoApp.assertOnUiThread();
-        }
-
-        // Handle composition text styles
-        if (text != null && text instanceof Spanned) {
-            Spanned span = (Spanned) text;
-            int spanStart = 0, spanEnd = 0;
-            boolean pastSelStart = false, pastSelEnd = false;
-
-            do {
-                int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT;
-                int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0;
-
-                // Find next offset where there is a style transition
-                spanEnd = span.nextSpanTransition(spanStart + 1, text.length(),
-                    CharacterStyle.class);
-
-                // Empty range, continue
-                if (spanEnd <= spanStart)
-                    continue;
-
-                // Get and iterate through list of span objects within range
-                CharacterStyle[] styles = span.getSpans(spanStart, spanEnd, CharacterStyle.class);
-
-                for (CharacterStyle style : styles) {
-                    if (style instanceof UnderlineSpan) {
-                        // Text should be underlined
-                        rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
-                    } else if (style instanceof ForegroundColorSpan) {
-                        // Text should be of a different foreground color
-                        rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
-                        rangeForeColor = ((ForegroundColorSpan) style).getForegroundColor();
-                    } else if (style instanceof BackgroundColorSpan) {
-                        // Text should be of a different background color
-                        rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
-                        rangeBackColor = ((BackgroundColorSpan) style).getBackgroundColor();
-                    }
-                }
-
-                // Add range to array, the actual styles are
-                //  applied when IME_SET_TEXT is sent
-                if (DEBUG) {
-                    Log.d(LOGTAG, String.format(
-                          ". . . sendTextToGecko: IME_ADD_RANGE, %d, %d, %d, %d, %d, %d",
-                          spanStart, spanEnd - spanStart, rangeType, rangeStyles, rangeForeColor,
-                          rangeBackColor));
-                }
-
-                GeckoAppShell.sendEventToGecko(
-                    GeckoEvent.createIMERangeEvent(spanStart, spanEnd - spanStart,
-                                                  rangeType, rangeStyles,
-                                                  rangeForeColor, rangeBackColor));
-
-                spanStart = spanEnd;
-            } while (spanStart < text.length());
-        } else {
-            if (DEBUG) Log.d(LOGTAG, ". . . sendTextToGecko: IME_ADD_RANGE, 0, " + text.length()
-                                     + ", IME_RANGE_RAWINPUT, IME_RANGE_UNDERLINE)");
-            GeckoAppShell.sendEventToGecko(
-                GeckoEvent.createIMERangeEvent(0, text == null ? 0 : text.length(),
-                                               GeckoEvent.IME_RANGE_RAWINPUT,
-                                               GeckoEvent.IME_RANGE_UNDERLINE, 0, 0));
-        }
-
-        // Change composition (treating selection end as where the caret is)
-        if (DEBUG) {
-            Log.d(LOGTAG, ". . . sendTextToGecko: IME_SET_TEXT, IME_RANGE_CARETPOSITION, \""
-                          + text + "\")");
-        }
-
-        GeckoAppShell.sendEventToGecko(
-            GeckoEvent.createIMERangeEvent(caretPos, 0,
-                                           GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0,
-                                           text.toString()));
-    }
-
-    public void afterTextChanged(Editable s) {
-    }
-
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
         outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
         outAttrs.actionLabel = null;
 
         if (mIMEState == IME_STATE_PASSWORD)
             outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
         else if (mIMETypeHint.equalsIgnoreCase("url"))
@@ -857,23 +305,16 @@ class GeckoInputConnection
         DisplayMetrics metrics = app.getResources().getDisplayMetrics();
         if (Math.min(metrics.widthPixels, metrics.heightPixels) > INLINE_IME_MIN_DISPLAY_SIZE) {
             // prevent showing full-screen keyboard only when the screen is tall enough
             // to show some reasonable amount of the page (see bug 752709)
             outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI
                                    | EditorInfo.IME_FLAG_NO_FULLSCREEN;
         }
 
-        // onCreateInputConnection() can be called during composition when input focus
-        // is restored from a VKB popup window (such as for entering accented characters)
-        // back to our IME. We want to commit our active composition string. Bug 756429
-        if (hasCompositionString()) {
-            endComposition();
-        }
-
         String prevInputMethod = mCurrentInputMethod;
         mCurrentInputMethod = InputMethods.getCurrentInputMethod(app);
         if (DEBUG) {
             Log.d(LOGTAG, "IME: CurrentInputMethod=" + mCurrentInputMethod);
         }
 
         // If the user has changed IMEs, then notify input method observers.
         if (mCurrentInputMethod != prevInputMethod) {
@@ -883,55 +324,34 @@ class GeckoInputConnection
             }
         }
 
         resetCompositionState();
         return this;
     }
 
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        if (InputMethods.canUseInputMethodOnHKB(mCurrentInputMethod))
-            return false;
-
-        switch (event.getAction()) {
-            case KeyEvent.ACTION_DOWN:
-                return processKeyDown(keyCode, event);
-            case KeyEvent.ACTION_UP:
-                return processKeyUp(keyCode, event);
-            case KeyEvent.ACTION_MULTIPLE:
-                return onKeyMultiple(keyCode, event.getRepeatCount(), event);
-        }
         return false;
     }
 
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         return processKeyDown(keyCode, event);
     }
 
     private boolean processKeyDown(int keyCode, KeyEvent event) {
-        if (DEBUG) {
-            Log.d(LOGTAG, "IME: processKeyDown(keyCode=" + keyCode + ", event=" + event + ")");
-        }
-
         if (keyCode > KeyEvent.getMaxKeyCode())
             return false;
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_MENU:
             case KeyEvent.KEYCODE_BACK:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_SEARCH:
                 return false;
-            case KeyEvent.KEYCODE_DEL:
-                // See comments in GeckoInputConnection.onKeyDel
-                if (onKeyDel()) {
-                    return true;
-                }
-                break;
             case KeyEvent.KEYCODE_ENTER:
                 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
                     mIMEActionHint.equalsIgnoreCase("next"))
                     event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
                 break;
             default:
                 break;
         }
@@ -940,36 +360,27 @@ class GeckoInputConnection
         KeyListener keyListener = TextKeyListener.getInstance();
 
         // KeyListener returns true if it handled the event for us.
         if (mIMEState == IME_STATE_DISABLED ||
                 keyCode == KeyEvent.KEYCODE_ENTER ||
                 keyCode == KeyEvent.KEYCODE_DEL ||
                 keyCode == KeyEvent.KEYCODE_TAB ||
                 (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
-                !keyListener.onKeyDown(view, mEditable, keyCode, event)) {
-            // Make sure selection in Gecko is up-to-date
-            Span selection = getSelection();
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION,
-                                                                     selection.start,
-                                                                     selection.length));
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
+                !keyListener.onKeyDown(view, getEditable(), keyCode, event)) {
+            mEditableClient.sendEvent(GeckoEvent.createKeyEvent(event));
         }
         return true;
     }
 
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         return processKeyUp(keyCode, event);
     }
 
     private boolean processKeyUp(int keyCode, KeyEvent event) {
-        if (DEBUG) {
-            Log.d(LOGTAG, "IME: processKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
-        }
-
         if (keyCode > KeyEvent.getMaxKeyCode())
             return false;
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK:
             case KeyEvent.KEYCODE_SEARCH:
             case KeyEvent.KEYCODE_MENU:
                 return false;
@@ -979,25 +390,30 @@ class GeckoInputConnection
 
         View view = getView();
         KeyListener keyListener = TextKeyListener.getInstance();
 
         if (mIMEState == IME_STATE_DISABLED ||
             keyCode == KeyEvent.KEYCODE_ENTER ||
             keyCode == KeyEvent.KEYCODE_DEL ||
             (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
-            !keyListener.onKeyUp(view, mEditable, keyCode, event)) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
+            !keyListener.onKeyUp(view, getEditable(), keyCode, event)) {
+            mEditableClient.sendEvent(GeckoEvent.createKeyEvent(event));
         }
 
         return true;
     }
 
     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
+        while ((repeatCount--) != 0) {
+            if (!processKeyDown(keyCode, event) ||
+                !processKeyUp(keyCode, event)) {
+                return false;
+            }
+        }
         return true;
     }
 
     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
         View v = getView();
         switch (keyCode) {
             case KeyEvent.KEYCODE_MENU:
                 InputMethodManager imm = getInputMethodManager();
@@ -1011,112 +427,84 @@ class GeckoInputConnection
     }
 
     public boolean isIMEEnabled() {
         // make sure this picks up PASSWORD and PLUGIN states as well
         return mIMEState != IME_STATE_DISABLED;
     }
 
     public void notifyIME(final int type, final int state) {
-        postToUiThread(new Runnable() {
-            public void run() {
-                View v = getView();
-                if (v == null)
-                    return;
+
+        final View v = getView();
+        if (v == null)
+            return;
 
-                switch (type) {
-                    case NOTIFY_IME_RESETINPUTSTATE:
-                        if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: reset");
+        switch (type) {
+            case NOTIFY_IME_RESETINPUTSTATE:
+                if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: reset");
 
-                        // Gecko just cancelled the current composition from underneath us,
-                        // so abandon our active composition string WITHOUT committing it!
-                        resetCompositionState();
+                resetCompositionState();
 
-                        // Don't use IMEStateUpdater for reset.
-                        // Because IME may not work showSoftInput()
-                        // after calling restartInput() immediately.
-                        // So we have to call showSoftInput() delay.
-                        InputMethodManager imm = getInputMethodManager();
-                        if (imm == null) {
-                            // no way to reset IME status directly
-                            IMEStateUpdater.resetIME();
-                        } else {
-                            imm.restartInput(v);
-                        }
+                // Don't use IMEStateUpdater for reset.
+                // Because IME may not work showSoftInput()
+                // after calling restartInput() immediately.
+                // So we have to call showSoftInput() delay.
+                InputMethodManager imm = getInputMethodManager();
+                if (imm == null) {
+                    // no way to reset IME status directly
+                    IMEStateUpdater.resetIME();
+                } else {
+                    imm.restartInput(v);
+                }
 
-                        // keep current enabled state
-                        IMEStateUpdater.enableIME();
-                        break;
+                // keep current enabled state
+                IMEStateUpdater.enableIME();
+                break;
 
-                    case NOTIFY_IME_CANCELCOMPOSITION:
-                        if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: cancel");
-                        IMEStateUpdater.resetIME();
-                        break;
+            case NOTIFY_IME_CANCELCOMPOSITION:
+                if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: cancel");
+                removeComposingSpans(getEditable());
+                break;
 
-                    case NOTIFY_IME_FOCUSCHANGE:
-                        if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: focus");
-                        IMEStateUpdater.resetIME();
-                        break;
+            case NOTIFY_IME_FOCUSCHANGE:
+                if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: focus");
+                IMEStateUpdater.resetIME();
+                break;
 
-                    case NOTIFY_IME_SETOPENSTATE:
-                    default:
-                        if (DEBUG)
-                            throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type);
-                        break;
+            default:
+                if (DEBUG) {
+                    throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type);
                 }
-            }
-        });
+                break;
+        }
     }
 
-    public void notifyIMEEnabled(final int state, final String typeHint, final String modeHint, final String actionHint) {
+    public void notifyIMEEnabled(final int state, final String typeHint,
+                                 final String modeHint, final String actionHint) {
         // For some input type we will use a  widget to display the ui, for those we must not
         // display the ime. We can display a widget for date and time types and, if the sdk version
         // is greater than 11, for datetime/month/week as well.
         if (typeHint.equals("date") || typeHint.equals("time") ||
             (Build.VERSION.SDK_INT > 10 &&
             (typeHint.equals("datetime") || typeHint.equals("month") ||
             typeHint.equals("week") || typeHint.equals("datetime-local")))) {
             return;
         }
 
-        postToUiThread(new Runnable() {
-            public void run() {
-                View v = getView();
-                if (v == null)
-                    return;
-
-                /* When IME is 'disabled', IME processing is disabled.
-                   In addition, the IME UI is hidden */
-                mIMEState = state;
-                mIMETypeHint = (typeHint == null) ? "" : typeHint;
-                mIMEModeHint = (modeHint == null) ? "" : modeHint;
-                mIMEActionHint = (actionHint == null) ? "" : actionHint;
-                IMEStateUpdater.enableIME();
-            }
-        });
-    }
+        final View v = getView();
+        if (v == null)
+            return;
 
-    public final void notifyIMEChange(final String text, final int start, final int end,
-                                      final int newEnd) {
-        if (newEnd < 0) {
-            // FIXME: Post notifySelectionChange() to UI thread after bug 780543 is fixed.
-            // notifyIMEChange() is called on the Gecko thread. We want to run all
-            // InputMethodManager code on the UI thread to avoid IME race conditions that cause
-            // crashes like bug 747629. However, if notifySelectionChange() is run on the UI thread,
-            // it causes mysterious problems with repeating characters like bug 780543. This
-            // band-aid fix is to run all InputMethodManager code on the UI thread except
-            // notifySelectionChange() until I can find the root cause.
-            notifySelectionChange(start, end);
-        } else {
-            postToUiThread(new Runnable() {
-                public void run() {
-                    notifyTextChange(text, start, end, newEnd);
-                }
-            });
-        }
+        /* When IME is 'disabled', IME processing is disabled.
+           In addition, the IME UI is hidden */
+        mIMEState = state;
+        mIMETypeHint = (typeHint == null) ? "" : typeHint;
+        mIMEModeHint = (modeHint == null) ? "" : modeHint;
+        mIMEActionHint = (actionHint == null) ? "" : actionHint;
+        IMEStateUpdater.enableIME();
     }
 
     /* Delay updating IME states (see bug 573800) */
     private static final class IMEStateUpdater extends TimerTask {
         private static IMEStateUpdater instance;
         private boolean mEnable;
         private boolean mReset;
 
@@ -1132,17 +520,17 @@ class GeckoInputConnection
             getInstance().mEnable = true;
         }
 
         public static synchronized void resetIME() {
             getInstance().mReset = true;
         }
 
         public void run() {
-            if (DEBUG) Log.d(LOGTAG, "IME: run()");
+            if (DEBUG) Log.d(LOGTAG, "IME: IMEStateUpdater.run()");
             synchronized (IMEStateUpdater.class) {
                 instance = null;
             }
 
             // TimerTask.run() is running on a random background thread, so post to UI thread.
             postToUiThread(new Runnable() {
                 public void run() {
                     final View v = getView();
@@ -1163,339 +551,82 @@ class GeckoInputConnection
                         imm.showSoftInput(v, 0);
                     } else if (imm.isActive(v)) {
                         imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                     }
                 }
             });
         }
     }
-
-    private void setEditable(String contents) {
-        int prevLength = mEditable.length();
-        mEditable.removeSpan(this);
-        mEditable.replace(0, prevLength, contents);
-        spanAndSelectEditable();
-    }
-
-    private void spanAndSelectEditable() {
-        int length = mEditable.length();
-        mEditable.setSpan(this, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        Selection.setSelection(mEditable, length);
-    }
+}
 
-    protected final boolean hasCompositionString() {
-        return mCompositionStart != NO_COMPOSITION_STRING;
-    }
-
-    private Span getComposingSpan() {
-        int start = getComposingSpanStart(mEditable);
-        int end = getComposingSpanEnd(mEditable);
-
-        // Does the editable have a composing span?
-        if (start < 0 || end < 0) {
-            if (start != -1 || end != -1) {
-                throw new IndexOutOfBoundsException("Bad composing span [" + start + "," + end
-                                                     + "), contentLength=" + mEditable.length());
-            }
-            return null;
-        }
-
-        return new Span(start, end, mEditable);
-    }
+final class DebugGeckoInputConnection
+        extends GeckoInputConnection
+        implements InvocationHandler {
 
-    private static String prettyPrintString(CharSequence s) {
-        // Quote string and replace newlines with CR arrows.
-        return "\"" + s.toString().replace('\n', UNICODE_CRARR) + "\"";
-    }
-
-    private static final class Span {
-        public final int start;
-        public final int end;
-        public final int length;
-
-        public static Span clamp(int start, int end, Editable content) {
-            return new Span(start, end, content);
-        }
+    private InputConnection mProxy;
 
-        private Span(int a, int b, Editable content) {
-            if (a > b) {
-                int tmp = a;
-                a = b;
-                b = tmp;
-            }
-
-            final int contentLength = content.length();
-
-            if (a < 0) {
-                a = 0;
-            } else if (a > contentLength) {
-                a = contentLength;
-            }
-
-            if (b < 0) {
-                b = 0;
-            } else if (b > contentLength) {
-                b = contentLength;
-            }
-
-            start = a;
-            end = b;
-            length = end - start;
-        }
+    private DebugGeckoInputConnection(View targetView,
+                                      GeckoEditableClient editable) {
+        super(targetView, editable);
     }
 
-private static final class DebugGeckoInputConnection extends GeckoInputConnection {
-    public DebugGeckoInputConnection(View targetView) {
-        super(targetView);
-        GeckoApp.assertOnUiThread();
-    }
-
-    @Override
-    public boolean beginBatchEdit() {
-        Log.d(LOGTAG, "IME: beginBatchEdit: mBatchEditCount " + mBatchEditCount
-                                                     + " -> " + (mBatchEditCount+1));
-        GeckoApp.assertOnUiThread();
-        return super.beginBatchEdit();
-    }
-
-    @Override
-    public boolean endBatchEdit() {
-        Log.d(LOGTAG, "IME: endBatchEdit: mBatchEditCount " + mBatchEditCount
-                                                   + " -> " + (mBatchEditCount-1));
-        GeckoApp.assertOnUiThread();
-        if (mBatchEditCount <= 0) {
-            throw new IllegalStateException("Expected positive mBatchEditCount, but got "
-                                            + mBatchEditCount);
-        }
-        return super.endBatchEdit();
-    }
-
-    @Override
-    public boolean commitCompletion(CompletionInfo text) {
-        Log.d(LOGTAG, "IME: commitCompletion");
-        GeckoApp.assertOnUiThread();
-        return super.commitCompletion(text);
-    }
-
-    @Override
-    public boolean commitText(CharSequence text, int newCursorPosition) {
-        Log.d(LOGTAG, String.format("IME: commitText(\"%s\", %d)", text, newCursorPosition));
-        GeckoApp.assertOnUiThread();
-        return super.commitText(text, newCursorPosition);
-    }
-
-    @Override
-    public boolean deleteSurroundingText(int leftLength, int rightLength) {
-        Log.d(LOGTAG, "IME: deleteSurroundingText(leftLen=" + leftLength + ", rightLen="
-                      + rightLength + ")");
-        GeckoApp.assertOnUiThread();
-        return super.deleteSurroundingText(leftLength, rightLength);
-    }
-
-    @Override
-    public boolean finishComposingText() {
-        Log.d(LOGTAG, "IME: finishComposingText");
-        // finishComposingText will post itself to the ui thread,
-        // no need to assert it.
-        return super.finishComposingText();
-    }
-
-    @Override
-    public Editable getEditable() {
-        Editable editable = super.getEditable();
-        Log.d(LOGTAG, "IME: getEditable -> " + prettyPrintString(editable));
-        // FIXME: Uncomment assert after bug 780543 is fixed. //GeckoApp.assertOnUiThread();
-        return editable;
-    }
-
-    @Override
-    public boolean performContextMenuAction(int id) {
-        Log.d(LOGTAG, "IME: performContextMenuAction");
-        GeckoApp.assertOnUiThread();
-        return super.performContextMenuAction(id);
-    }
-
-    @Override
-    public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
-        Log.d(LOGTAG, "IME: getExtractedText");
-        GeckoApp.assertOnUiThread();
-        ExtractedText extract = super.getExtractedText(req, flags);
-        if (extract != null)
-            Log.d(LOGTAG, String.format(
-                          ". . . getExtractedText: extract.text=\"%s\", selStart=%d, selEnd=%d",
-                          extract.text, extract.selectionStart, extract.selectionEnd));
-        return extract;
+    public static InputConnectionHandler create(View targetView,
+                                                GeckoEditableClient editable) {
+        final Class[] PROXY_INTERFACES = { InputConnection.class,
+                InputConnectionHandler.class,
+                GeckoEditableListener.class };
+        DebugGeckoInputConnection dgic =
+                new DebugGeckoInputConnection(targetView, editable);
+        dgic.mProxy = (InputConnection)Proxy.newProxyInstance(
+                GeckoInputConnection.class.getClassLoader(),
+                PROXY_INTERFACES, dgic);
+        editable.setListener((GeckoEditableListener)dgic.mProxy);
+        return (InputConnectionHandler)dgic.mProxy;
     }
 
-    @Override
-    public CharSequence getTextAfterCursor(int length, int flags) {
-        Log.d(LOGTAG, "IME: getTextAfterCursor(length=" + length + ", flags=" + flags + ")");
-        GeckoApp.assertOnUiThread();
-        CharSequence s = super.getTextAfterCursor(length, flags);
-        Log.d(LOGTAG, ". . . getTextAfterCursor returns \"" + s + "\"");
-        return s;
-    }
-
-    @Override
-    public CharSequence getTextBeforeCursor(int length, int flags) {
-        Log.d(LOGTAG, "IME: getTextBeforeCursor");
-        GeckoApp.assertOnUiThread();
-        CharSequence s = super.getTextBeforeCursor(length, flags);
-        Log.d(LOGTAG, ". . . getTextBeforeCursor returns \"" + s + "\"");
-        return s;
-    }
-
-    @Override
-    public boolean setComposingText(CharSequence text, int newCursorPosition) {
-        Log.d(LOGTAG, String.format("IME: setComposingText(\"%s\", %d)", text, newCursorPosition));
-        GeckoApp.assertOnUiThread();
-        return super.setComposingText(text, newCursorPosition);
-    }
-
-    @Override
-    public boolean setComposingRegion(int start, int end) {
-        Log.d(LOGTAG, "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
-        GeckoApp.assertOnUiThread();
-        return super.setComposingRegion(start, end);
-    }
-
-    @Override
-    public boolean setSelection(int start, int end) {
-        Log.d(LOGTAG, "IME: setSelection(start=" + start + ", end=" + end + ")");
-        GeckoApp.assertOnUiThread();
-        return super.setSelection(start, end);
-    }
-
-    @Override
-    public String getComposingText() {
-        Log.d(LOGTAG, "IME: getComposingText");
-        GeckoApp.assertOnUiThread();
-        String s = super.getComposingText();
-        Log.d(LOGTAG, ". . . getComposingText: Composing text = \"" + s + "\"");
-        return s;
-    }
-
-    @Override
-    public boolean onKeyDel() {
-        Log.d(LOGTAG, "IME: onKeyDel");
-        GeckoApp.assertOnUiThread();
-        return super.onKeyDel();
-    }
-
-    @Override
-    protected void notifyTextChange(String text, int start, int oldEnd, int newEnd) {
-        // notifyTextChange() call is posted to UI thread from notifyIMEChange().
-        GeckoApp.assertOnUiThread();
-        String msg = String.format("IME: >notifyTextChange(%s, start=%d, oldEnd=%d, newEnd=%d)",
-                                   prettyPrintString(text), start, oldEnd, newEnd);
-        Log.d(LOGTAG, msg);
-        if (start < 0 || oldEnd < start || newEnd < start || newEnd > text.length()) {
-            throw new IllegalArgumentException("BUG! " + msg);
+    private static StringBuilder debugAppend(StringBuilder sb, Object obj) {
+        if (obj == null) {
+            sb.append("null");
+        } else if (obj instanceof GeckoEditable) {
+            sb.append("GeckoEditable");
+        } else if (Proxy.isProxyClass(obj.getClass())) {
+            debugAppend(sb, Proxy.getInvocationHandler(obj));
+        } else if (obj instanceof CharSequence) {
+            sb.append("\"").append(obj.toString().replace('\n', '\u21b2')).append("\"");
+        } else if (obj.getClass().isArray()) {
+            Class cls = obj.getClass();
+            sb.append(cls.getComponentType().getSimpleName()).append("[")
+              .append(java.lang.reflect.Array.getLength(obj)).append("]");
+        } else {
+            sb.append(obj.toString());
         }
-        super.notifyTextChange(text, start, oldEnd, newEnd);
-    }
-
-    @Override
-    protected void notifySelectionChange(int start, int end) {
-        // notifySelectionChange() call is posted to UI thread from notifyIMEChange().
-        // FIXME: Uncomment assert after bug 780543 is fixed.
-        //GeckoApp.assertOnUiThread();
-        Log.d(LOGTAG, String.format("IME: >notifySelectionChange(start=%d, end=%d)", start, end));
-        super.notifySelectionChange(start, end);
+        return sb;
     }
 
-    @Override
-    protected void resetCompositionState() {
-        Log.d(LOGTAG, "IME: resetCompositionState");
-        GeckoApp.assertOnUiThread();
-        if (hasCompositionString()) {
-            Log.d(LOGTAG, "resetCompositionState() is abandoning an active composition string");
-        }
-        super.resetCompositionState();
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        Log.d(LOGTAG, String.format("IME: onTextChanged(\"%s\" start=%d, before=%d, count=%d)",
-                                    s, start, before, count));
-        GeckoApp.assertOnUiThread();
-        super.onTextChanged(s, start, before, count);
-    }
+    public Object invoke(Object proxy, Method method, Object[] args)
+            throws Throwable {
 
-    @Override
-    public void afterTextChanged(Editable s) {
-        Log.d(LOGTAG, "IME: afterTextChanged(\"" + s + "\")");
-        GeckoApp.assertOnUiThread();
-        super.afterTextChanged(s);
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        Log.d(LOGTAG, String.format("IME: beforeTextChanged(\"%s\", start=%d, count=%d, after=%d)",
-                                    s, start, count, after));
-        GeckoApp.assertOnUiThread();
-        super.beforeTextChanged(s, start, count, after);
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        Log.d(LOGTAG, "IME: onCreateInputConnection called");
-        GeckoApp.assertOnUiThread();
-        return super.onCreateInputConnection(outAttrs);
-    }
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        Log.d(LOGTAG, "IME: onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
         GeckoApp.assertOnUiThread();
-        return super.onKeyPreIme(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        Log.d(LOGTAG, "IME: onKeyDown(keyCode=" + keyCode + ", event=" + event + ")");
-        GeckoApp.assertOnUiThread();
-        return super.onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        Log.d(LOGTAG, "IME: onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
-        GeckoApp.assertOnUiThread();
-        return super.onKeyUp(keyCode, event);
-    }
+        Object ret = method.invoke(this, args);
+        if (ret == this) {
+            ret = mProxy;
+        }
 
-    @Override
-    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        Log.d(LOGTAG, "IME: onKeyMultiple(keyCode=" + keyCode + ", repeatCount=" + repeatCount
-                      + ", event=" + event + ")");
-        GeckoApp.assertOnUiThread();
-        return super.onKeyMultiple(keyCode, repeatCount, event);
-    }
-
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        Log.d(LOGTAG, "IME: onKeyLongPress(keyCode=" + keyCode + ", event=" + event + ")");
-        GeckoApp.assertOnUiThread();
-        return super.onKeyLongPress(keyCode, event);
-    }
+        StringBuilder log = new StringBuilder(method.getName());
+        log.append("(");
+        for (Object arg : args) {
+            debugAppend(log, arg).append(", ");
+        }
+        if (args.length > 0) {
+            log.setLength(log.length() - 2);
+        }
+        if (method.getReturnType().equals(Void.TYPE)) {
+            log.append(")");
+        } else {
+            debugAppend(log.append(") = "), ret);
+        }
+        Log.d(LOGTAG, log.toString());
 
-    @Override
-    public void notifyIME(int type, int state) {
-        Log.d(LOGTAG, "IME: >notifyIME(type=" + type + ", state=" + state + ")");
-        GeckoApp.assertOnGeckoThread();
-        super.notifyIME(type, state);
-    }
-
-    @Override
-    public void notifyIMEEnabled(int state, String typeHint, String modeHint, String actionHint) {
-        Log.d(LOGTAG, "IME: >notifyIMEEnabled(state=" + state + ", typeHint=\"" + typeHint
-                      + "\", modeHint=\"" + modeHint + "\", actionHint=\""
-                      + actionHint + "\"");
-        GeckoApp.assertOnGeckoThread();
-        if (state < IME_STATE_DISABLED || state > IME_STATE_PLUGIN)
-            throw new IllegalArgumentException("Unexpected IMEState=" + state);
-        super.notifyIMEEnabled(state, typeHint, modeHint, actionHint);
+        return ret;
     }
 }
 
-}
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -27,16 +27,17 @@ UTIL_JAVA_FILES := \
   GeckoBackgroundThread.java \
   GeckoEventListener.java \
   GeckoEventResponder.java \
   GeckoJarReader.java \
   INIParser.java \
   INISection.java \
   util/EventDispatcher.java \
   util/FloatUtils.java \
+  util/LruCache.java \
   $(NULL)
 
 FENNEC_JAVA_FILES = \
   AboutHomeContent.java \
   AboutHomePromoBox.java \
   AboutHomeSection.java \
   ActivityHandlerHelper.java \
   AndroidImport.java \
@@ -71,16 +72,17 @@ FENNEC_JAVA_FILES = \
   FontSizePreference.java \
   FormAssistPopup.java \
   GeckoAccessibility.java \
   GeckoApplication.java \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoBatteryManager.java \
   GeckoConnectivityReceiver.java \
+  GeckoEditable.java \
   GeckoEvent.java \
   GeckoHalDefines.java \
   GeckoInputConnection.java \
   GeckoMenu.java \
   GeckoMenuInflater.java \
   GeckoMenuItem.java \
   GeckoMessageReceiver.java \
   GeckoSubMenu.java \
--- a/mobile/android/base/MemoryMonitor.java
+++ b/mobile/android/base/MemoryMonitor.java
@@ -151,16 +151,18 @@ class MemoryMonitor extends BroadcastRec
 
         // TODO hook in memory-reduction stuff for different levels here
         if (level >= MEMORY_PRESSURE_MEDIUM) {
             if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
                 GeckoAppShell.onLowMemory();
             }
             ScreenshotHandler.disableScreenshot(false);
             GeckoAppShell.geckoEventSync();
+
+            GeckoApp.mAppContext.getFavicons().clearMemCache();
         }
     }
 
     private boolean decreaseMemoryPressure() {
         int newLevel;
         synchronized (this) {
             if (mMemoryPressure <= 0) {
                 return false;
--- a/mobile/android/base/ScreenshotHandler.java
+++ b/mobile/android/base/ScreenshotHandler.java
@@ -30,17 +30,17 @@ public final class ScreenshotHandler imp
     public static final int SCREENSHOT_CHECKERBOARD = 1;
 
     private static final String SCREENSHOT_DISABLED_PREF = "gfx.java.screenshot.enabled";
 
     private static final String LOGTAG = "GeckoScreenshotHandler";
     private static final int BYTES_FOR_16BPP = 2;
     private static final int MAX_PIXELS_PER_SLICE = 100000;
 
-    private static boolean sDisableScreenshot;
+    private static boolean sDisableScreenshot = true;
     private static boolean sForceDisabled;
     private static ScreenshotHandler sInstance;
 
     private final int mMaxTextureSize;
     private final int mMinTextureSize;
     private final int mMaxPixels;
 
     private final Queue<PendingScreenshot> mPendingScreenshots;
--- a/mobile/android/base/awesomebar/AllPagesTab.java
+++ b/mobile/android/base/awesomebar/AllPagesTab.java
@@ -2,29 +2,36 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.AwesomeBar.ContextMenuSubject;
 import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserContract.Images;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.util.GeckoAsyncTask;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Context;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.TranslateAnimation;
 import android.view.ContextMenu;
@@ -44,16 +51,17 @@ import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.SimpleCursorAdapter;
 import android.widget.TabHost.TabContentFactory;
 import android.widget.TextView;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 
 public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
     public static final String LOGTAG = "ALL_PAGES";
     private static final String TAG = "allPages";
 
     private static final int SUGGESTION_TIMEOUT = 3000;
     private static final int SUGGESTION_MAX = 3;
     private static final int ANIMATION_DURATION = 250;
@@ -63,16 +71,21 @@ public class AllPagesTab extends Awesome
     private SuggestClient mSuggestClient;
     private boolean mSuggestionsEnabled;
     private AsyncTask<String, Void, ArrayList<String>> mSuggestTask;
     private AwesomeBarCursorAdapter mCursorAdapter = null;
     private boolean mTelemetrySent = false;
     private LinearLayout mAllPagesView;
     private boolean mAnimateSuggestions;
     private View mSuggestionsOptInPrompt;
+    private Handler mHandler;
+
+    private static final int MESSAGE_LOAD_FAVICONS = 1;
+    private static final int MESSAGE_UPDATE_FAVICONS = 2;
+    private static final int DELAY_SHOW_THUMBNAILS = 550;
 
     private class SearchEntryViewHolder {
         public FlowLayout suggestionView;
         public ImageView iconView;
         public LinearLayout userEnteredView;
         public TextView userEnteredTextView;
     }
 
@@ -117,32 +130,41 @@ public class AllPagesTab extends Awesome
     }
 
     public ListView getListView() {
         if (mView == null) {
             mView = getAllPagesView().findViewById(R.id.awesomebar_list);
             ((Activity)mContext).registerForContextMenu(mView);
             mView.setTag(TAG);
             AwesomeBarCursorAdapter adapter = getCursorAdapter();
-            ((ListView)mView).setAdapter(adapter);
-            mView.setOnTouchListener(mListListener);
+
+            ListView listView = (ListView) mView;
+            listView.setAdapter(adapter);
+            listView.setOnTouchListener(mListListener);
+
+            mHandler = new AllPagesHandler();
         }
+
         return (ListView)mView;
     }
 
     public void destroy() {
         AwesomeBarCursorAdapter adapter = getCursorAdapter();
         unregisterEventListener("SearchEngines:Data");
         if (adapter == null) {
             return;
         }
 
         Cursor cursor = adapter.getCursor();
         if (cursor != null)
             cursor.close();
+
+        mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS);
+        mHandler.removeMessages(MESSAGE_LOAD_FAVICONS);
+        mHandler = null;
     }
 
     public void filter(String searchTerm) {
         AwesomeBarCursorAdapter adapter = getCursorAdapter();
         adapter.filter(searchTerm);
 
         filterSuggestions(searchTerm);
         if (mSuggestionsOptInPrompt != null) {
@@ -191,16 +213,18 @@ public class AllPagesTab extends Awesome
 
             mCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() {
                 public Cursor runQuery(CharSequence constraint) {
                     long start = SystemClock.uptimeMillis();
 
                     Cursor c = BrowserDB.filter(getContentResolver(), constraint, MAX_RESULTS);
                     c.getCount();
 
+                    postLoadFavicons();
+
                     long end = SystemClock.uptimeMillis();
                     int time = (int)(end - start);
                     Log.i(LOGTAG, "Got cursor in " + time + "ms");
 
                     if (!mTelemetrySent && TextUtils.isEmpty(constraint)) {
                         Telemetry.HistogramAdd("FENNEC_AWESOMEBAR_ALLPAGES_EMPTY_TIME", time);
                         mTelemetrySent = true;
                     }
@@ -393,18 +417,18 @@ public class AllPagesTab extends Awesome
 
                 position -= getSuggestEngineCount();
                 Cursor cursor = getCursor();
                 if (!cursor.moveToPosition(position))
                     throw new IllegalStateException("Couldn't move cursor to position " + position);
 
                 updateTitle(viewHolder.titleView, cursor);
                 updateUrl(viewHolder.urlView, cursor);
-                updateFavicon(viewHolder.faviconView, cursor);
                 updateBookmarkIcon(viewHolder.bookmarkIconView, cursor);
+                displayFavicon(viewHolder);
             }
 
             return convertView;
         }
 
         private void bindSearchEngineView(final SearchEngine engine, SearchEntryViewHolder viewHolder) {
             // when a suggestion is clicked, do a search
             OnClickListener clickListener = new OnClickListener() {
@@ -701,9 +725,134 @@ public class AllPagesTab extends Awesome
 
     private void registerEventListener(String event) {
         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
     }
 
     private void unregisterEventListener(String event) {
         GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
     }
+
+    private List<String> getUrlsWithoutFavicon() {
+        List<String> urls = new ArrayList<String>();
+
+        Cursor c = mCursorAdapter.getCursor();
+        if (c == null || !c.moveToFirst())
+            return urls;
+
+        do {
+            final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
+
+            // We only want to load favicons from DB if they are not in the
+            // memory cache yet.
+            Favicons favicons = GeckoApp.mAppContext.getFavicons();
+            if (favicons.getFaviconFromMemCache(url) != null)
+                continue;
+
+            urls.add(url);
+        } while (c.moveToNext());
+
+        return urls;
+    }
+
+    public void storeFaviconsInMemCache(Cursor c) {
+        try {
+            if (c == null || !c.moveToFirst())
+                return;
+
+            Favicons favicons = GeckoApp.mAppContext.getFavicons();
+
+            do {
+                final String url = c.getString(c.getColumnIndexOrThrow(Images.URL));
+                final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Images.FAVICON));
+                if (b == null)
+                    continue;
+
+                Bitmap favicon = BitmapFactory.decodeByteArray(b, 0, b.length);
+                if (favicon == null)
+                    continue;
+
+                Drawable faviconDrawable = new BitmapDrawable(getResources(), favicon);
+                favicons.putFaviconInMemCache(url, faviconDrawable);
+            } while (c.moveToNext());
+        } finally {
+            if (c != null)
+                c.close();
+        }
+    }
+
+    private void loadFaviconsForCurrentResults() {
+        final List<String> urls = getUrlsWithoutFavicon();
+        if (urls.size() == 0)
+            return;
+
+        (new GeckoAsyncTask<Void, Void, Cursor>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
+            @Override
+            public Cursor doInBackground(Void... params) {
+                return BrowserDB.getFaviconsForUrls(getContentResolver(), urls);
+            }
+
+            @Override
+            public void onPostExecute(Cursor c) {
+                storeFaviconsInMemCache(c);
+                postUpdateFavicons();
+            }
+        }).execute();
+    }
+
+    private void displayFavicon(AwesomeEntryViewHolder viewHolder) {
+        final String url = viewHolder.urlView.getText().toString();
+        Favicons favicons = GeckoApp.mAppContext.getFavicons();
+        viewHolder.faviconView.setImageDrawable(favicons.getFaviconFromMemCache(url));
+    }
+
+    private void updateFavicons() {
+        ListView listView = (ListView) mView;
+        for (int i = 0; i < listView.getChildCount(); i++) {
+            final View view = listView.getChildAt(i);
+            final Object tag = view.getTag();
+
+            if (tag == null || !(tag instanceof AwesomeEntryViewHolder))
+                continue;
+
+            final AwesomeEntryViewHolder viewHolder = (AwesomeEntryViewHolder) tag;
+            displayFavicon(viewHolder);
+        }
+
+        mView.invalidate();
+    }
+
+    private void postUpdateFavicons() {
+        if (mHandler == null)
+            return;
+
+        Message msg = mHandler.obtainMessage(MESSAGE_UPDATE_FAVICONS,
+                                             AllPagesTab.this);
+
+        mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS);
+        mHandler.sendMessage(msg);
+    }
+
+    private void postLoadFavicons() {
+        if (mHandler == null)
+            return;
+
+        Message msg = mHandler.obtainMessage(MESSAGE_LOAD_FAVICONS,
+                                             AllPagesTab.this);
+
+        mHandler.removeMessages(MESSAGE_LOAD_FAVICONS);
+        mHandler.sendMessageDelayed(msg, 200);
+    }
+
+    private class AllPagesHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_LOAD_FAVICONS:
+                    loadFaviconsForCurrentResults();
+                    break;
+                case MESSAGE_UPDATE_FAVICONS:
+                    updateFavicons();
+                    break;
+            }
+        }
+    }
 }
--- a/mobile/android/base/db/BrowserDB.java
+++ b/mobile/android/base/db/BrowserDB.java
@@ -7,16 +7,18 @@ package org.mozilla.gecko.db;
 
 import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
 
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.drawable.BitmapDrawable;
 
+import java.util.List;
+
 public class BrowserDB {
     public static String ABOUT_PAGES_URL_FILTER = "about:%";
 
     public static interface URLColumns {
         public static String URL = "url";
         public static String TITLE = "title";
         public static String FAVICON = "favicon";
         public static String THUMBNAIL = "thumbnail";
@@ -68,22 +70,26 @@ public class BrowserDB {
         public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
 
         public void addReadingListItem(ContentResolver cr, String title, String uri);
 
         public void removeReadingListItemWithURL(ContentResolver cr, String uri);
 
         public BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri);
 
+        public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls);
+
         public void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon);
 
         public void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail);
 
         public byte[] getThumbnailForUrl(ContentResolver cr, String uri);
 
+        public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls);
+
         public void removeThumbnails(ContentResolver cr);
 
         public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
 
         public void registerHistoryObserver(ContentResolver cr, ContentObserver observer);
     }
 
     static {
@@ -181,28 +187,36 @@ public class BrowserDB {
     public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
         sDb.removeReadingListItemWithURL(cr, uri);
     }
 
     public static BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri) {
         return sDb.getFaviconForUrl(cr, uri);
     }
 
+    public static Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
+        return sDb.getFaviconsForUrls(cr, urls);
+    }
+
     public static void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon) {
         sDb.updateFaviconForUrl(cr, uri, favicon);
     }
 
     public static void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail) {
         sDb.updateThumbnailForUrl(cr, uri, thumbnail);
     }
 
     public static byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
         return sDb.getThumbnailForUrl(cr, uri);
     }
 
+    public static Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
+        return sDb.getThumbnailsForUrls(cr, urls);
+    }
+
     public static void removeThumbnails(ContentResolver cr) {
         sDb.removeThumbnails(cr);
     }
 
     public static void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
         sDb.registerBookmarkObserver(cr, observer);
     }
 
--- a/mobile/android/base/db/BrowserProvider.java.in
+++ b/mobile/android/base/db/BrowserProvider.java.in
@@ -64,17 +64,17 @@ import android.text.TextUtils;
 import android.util.Log;
 
 public class BrowserProvider extends ContentProvider {
     private static final String LOGTAG = "GeckoBrowserProvider";
     private Context mContext;
 
     static final String DATABASE_NAME = "browser.db";
 
-    static final int DATABASE_VERSION = 11;
+    static final int DATABASE_VERSION = 12;
 
     // Maximum age of deleted records to be cleaned up (20 days in ms)
     static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
 
     // Number of records marked as deleted to be removed
     static final long DELETED_RECORDS_PURGE_LIMIT = 5;
 
     // How many records to reposition in a single query.
@@ -95,16 +95,18 @@ public class BrowserProvider extends Con
     static final String TABLE_IMAGES = "images";
 
     static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
     static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
     static final String TABLE_IMAGES_TMP = TABLE_IMAGES + "_tmp";
 
     static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
     static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
+
+    static final String VIEW_COMBINED = "combined";
     static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images";
 
     // Bookmark matches
     static final int BOOKMARKS = 100;
     static final int BOOKMARKS_ID = 101;
     static final int BOOKMARKS_FOLDER_ID = 102;
     static final int BOOKMARKS_PARENT = 103;
     static final int BOOKMARKS_POSITIONS = 104;
@@ -639,27 +641,95 @@ public class BrowserProvider extends Con
                         " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
                                     qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
                                         qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
                                         qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
                     ") LEFT OUTER JOIN " + TABLE_IMAGES +
                         " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL));
         }
 
+        private void createCombinedViewOn12(SQLiteDatabase db) {
+            debug("Creating " + VIEW_COMBINED + " view");
+
+            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
+                    " SELECT " + Combined.BOOKMARK_ID + ", " +
+                                 Combined.HISTORY_ID + ", " +
+                                 // We need to return an _id column because CursorAdapter requires it for its
+                                 // default implementation for the getItemId() method. However, since
+                                 // we're not using this feature in the parts of the UI using this view,
+                                 // we can just use 0 for all rows.
+                                 "0 AS " + Combined._ID + ", " +
+                                 Combined.URL + ", " +
+                                 Combined.TITLE + ", " +
+                                 Combined.VISITS + ", " +
+                                 Combined.DISPLAY + ", " +
+                                 Combined.DATE_LAST_VISITED +
+                    " FROM (" +
+                        // Bookmarks without history.
+                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                     "-1 AS " + Combined.HISTORY_ID + ", " +
+                                     "-1 AS " + Combined.VISITS + ", " +
+                                     "-1 AS " + Combined.DATE_LAST_VISITED +
+                        " FROM " + TABLE_BOOKMARKS +
+                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                        " UNION ALL" +
+                        // History with and without bookmark.
+                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                     // Prioritze bookmark titles over history titles, since the user may have
+                                     // customized the title for a bookmark.
+                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                     // Only use DISPLAY_READER if the matching bookmark entry inside reading
+                                     // list folder is not marked as deleted.
+                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
+                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
+                                        " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
+                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
+                    ")");
+
+            debug("Creating " + VIEW_COMBINED_WITH_IMAGES + " view");
+
+            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_IMAGES + " AS" +
+                    " SELECT *, " +
+                        qualifyColumn(TABLE_IMAGES, Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                        qualifyColumn(TABLE_IMAGES, Images.THUMBNAIL) + " AS " + Combined.THUMBNAIL +
+                    " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_IMAGES +
+                        " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL));
+        }
+
         @Override
         public void onCreate(SQLiteDatabase db) {
             debug("Creating browser.db: " + db.getPath());
 
             createBookmarksTable(db);
             createHistoryTable(db);
             createImagesTable(db);
+            createCombinedViewOn12(db);
 
             createBookmarksWithImagesView(db);
             createHistoryWithImagesView(db);
-            createCombinedWithImagesViewOn11(db);
 
             createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
                 R.string.bookmarks_folder_places, 0);
 
             createOrUpdateAllSpecialFolders(db);
 
             createDefaultBookmarks(db, "^bookmarkdefaults_title_");
         }
@@ -1086,16 +1156,23 @@ public class BrowserProvider extends Con
             db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_IMAGES);
 
             db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
                     + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
 
             createCombinedWithImagesViewOn11(db);
         }
 
+        private void upgradeDatabaseFrom11to12(SQLiteDatabase db) {
+            debug("Dropping view: " + VIEW_COMBINED_WITH_IMAGES);
+            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_IMAGES);
+
+            createCombinedViewOn12(db);
+        }
+
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             debug("Upgrading browser.db: " + db.getPath() + " from " +
                     oldVersion + " to " + newVersion);
 
             db.beginTransaction();
 
             // We have to do incremental upgrades until we reach the current
@@ -1136,16 +1213,20 @@ public class BrowserProvider extends Con
 
                     case 10:
                         upgradeDatabaseFrom9to10(db);
                         break;
 
                     case 11:
                         upgradeDatabaseFrom10to11(db);
                         break;
+
+                    case 12:
+                        upgradeDatabaseFrom11to12(db);
+                        break;
                  }
              }
 
              db.setTransactionSuccessful();
              db.endTransaction();
         }
 
         @Override
@@ -1926,17 +2007,21 @@ public class BrowserProvider extends Con
                 if (TextUtils.isEmpty(sortOrder))
                     sortOrder = DEFAULT_HISTORY_SORT_ORDER;
 
                 // This will avoid duplicate entries in the awesomebar
                 // results when a history entry has multiple bookmarks.
                 groupBy = Combined.URL;
 
                 qb.setProjectionMap(COMBINED_PROJECTION_MAP);
-                qb.setTables(VIEW_COMBINED_WITH_IMAGES);
+
+                if (hasImagesInProjection(projection))
+                    qb.setTables(VIEW_COMBINED_WITH_IMAGES);
+                else
+                    qb.setTables(VIEW_COMBINED);
 
                 break;
             }
 
             case CONTROL: {
                 debug("Query is on control: " + uri);
 
                 Cursor controlCursor =
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -26,16 +26,17 @@ import android.graphics.drawable.BitmapD
 import android.net.Uri;
 import android.provider.Browser;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 
 public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
     // Calculate these once, at initialization. isLoggable is too expensive to
     // have in-line in each log call.
     private static final String LOGTAG = "GeckoLocalBrowserDB";
     private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
     protected static void debug(String message) {
         if (logDebug) {
@@ -176,31 +177,29 @@ public class LocalBrowserDB implements B
         return new LocalDBCursor(c);
     }
 
     public Cursor filter(ContentResolver cr, CharSequence constraint, int limit) {
         return filterAllSites(cr,
                               new String[] { Combined._ID,
                                              Combined.URL,
                                              Combined.TITLE,
-                                             Combined.FAVICON,
                                              Combined.DISPLAY,
                                              Combined.BOOKMARK_ID,
                                              Combined.HISTORY_ID },
                               constraint,
                               limit,
                               null);
     }
 
     public Cursor getTopSites(ContentResolver cr, int limit) {
         return filterAllSites(cr,
                               new String[] { Combined._ID,
                                              Combined.URL,
-                                             Combined.TITLE,
-                                             Combined.THUMBNAIL },
+                                             Combined.TITLE },
                               "",
                               limit,
                               BrowserDB.ABOUT_PAGES_URL_FILTER);
     }
 
     public void updateVisitedHistory(ContentResolver cr, String uri) {
         ContentValues values = new ContentValues();
 
@@ -614,16 +613,37 @@ public class LocalBrowserDB implements B
 
         if (b == null)
             return null;
 
         Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
         return new BitmapDrawable(bitmap);
     }
 
+    public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
+        StringBuffer selection = new StringBuffer();
+        String[] selectionArgs = new String[urls.size()];
+
+        for (int i = 0; i < urls.size(); i++) {
+          final String url = urls.get(i);
+
+          if (i > 0)
+            selection.append(" OR ");
+
+          selection.append(Images.URL + " = ?");
+          selectionArgs[i] = url;
+        }
+
+        return cr.query(mImagesUriWithProfile,
+                        new String[] { Images.URL, Images.FAVICON },
+                        selection.toString(),
+                        selectionArgs,
+                        null);
+    }
+
     public void updateFaviconForUrl(ContentResolver cr, String uri,
             BitmapDrawable favicon) {
         Bitmap bitmap = favicon.getBitmap();
         if (bitmap == null)
             return;
 
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
@@ -682,16 +702,37 @@ public class LocalBrowserDB implements B
         int thumbnailIndex = c.getColumnIndexOrThrow(Images.THUMBNAIL);
 
         byte[] b = c.getBlob(thumbnailIndex);
         c.close();
 
         return b;
     }
 
+    public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
+        StringBuffer selection = new StringBuffer();
+        String[] selectionArgs = new String[urls.size()];
+
+        for (int i = 0; i < urls.size(); i++) {
+          final String url = urls.get(i);
+
+          if (i > 0)
+            selection.append(" OR ");
+
+          selection.append(Images.URL + " = ?");
+          selectionArgs[i] = url;
+        }
+
+        return cr.query(mImagesUriWithProfile,
+                        new String[] { Images.URL, Images.THUMBNAIL },
+                        selection.toString(),
+                        selectionArgs,
+                        null);
+    }
+
     public void removeThumbnails(ContentResolver cr) {
         ContentValues values = new ContentValues();
         values.putNull(Images.THUMBNAIL);
         cr.update(mImagesUriWithProfile, values, null, null);
     }
 
     // Utility function for updating existing history using batch operations
     public void updateHistoryInBatch(ContentResolver cr,
--- a/mobile/android/base/gfx/InputConnectionHandler.java
+++ b/mobile/android/base/gfx/InputConnectionHandler.java
@@ -11,9 +11,10 @@ import android.view.inputmethod.InputCon
 public interface InputConnectionHandler
 {
     InputConnection onCreateInputConnection(EditorInfo outAttrs);
     boolean onKeyPreIme(int keyCode, KeyEvent event);
     boolean onKeyDown(int keyCode, KeyEvent event);
     boolean onKeyLongPress(int keyCode, KeyEvent event);
     boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
     boolean onKeyUp(int keyCode, KeyEvent event);
+    boolean isIMEEnabled();
 }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -256,16 +256,23 @@ public class LayerView extends FrameLayo
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         if (mInputConnectionHandler != null)
             return mInputConnectionHandler.onKeyUp(keyCode, event);
         return false;
     }
 
+    public boolean isIMEEnabled() {
+        if (mInputConnectionHandler != null) {
+            return mInputConnectionHandler.isIMEEnabled();
+        }
+        return false;
+    }
+
     public void requestRender() {
         if (mListener != null) {
             mListener.renderRequested();
         }
     }
 
     public void addLayer(Layer layer) {
         mRenderer.addLayer(layer);
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -213,17 +213,17 @@
       <item name="android:layout_centerHorizontal">true</item>
       <item name="android:layout_alignParentTop">true</item>
       <item name="android:src">@drawable/abouthome_thumbnail_bg</item>
       <item name="android:background">#5FFF</item>
       <item name="android:paddingTop">0dip</item>
       <item name="android:paddingBottom">@dimen/abouthome_icon_radius</item>
       <item name="android:paddingLeft">0dip</item>
       <item name="android:paddingRight">0dip</item>
-      <item name="android:scaleType">centerCrop</item>
+      <item name="android:scaleType">fitCenter</item>
     </style>
 
     <style name="AboutHome.Thumbnail.Label">
       <item name="android:layout_width">fill_parent</item>
       <item name="android:layout_height">18dip</item>
       <item name="android:layout_alignBottom">@id/thumbnail</item>
       <item name="android:background">#EFFF</item>
       <item name="android:singleLine">true</item>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/util/LruCache.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mozilla.gecko.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Static library version of {@link android.util.LruCache}. Used to write apps
+ * that run on API levels prior to 12. When running on API level 12 or above,
+ * this implementation is still used; it does not try to switch to the
+ * framework's implementation. See the framework SDK documentation for a class
+ * overview.
+ */
+public class LruCache<K, V> {
+    private final LinkedHashMap<K, V> map;
+
+    /** Size of this cache in units. Not necessarily the number of elements. */
+    private int size;
+    private int maxSize;
+
+    private int putCount;
+    private int createCount;
+    private int evictionCount;
+    private int hitCount;
+    private int missCount;
+
+    /**
+     * @param maxSize for caches that do not override {@link #sizeOf}, this is
+     *     the maximum number of entries in the cache. For all other caches,
+     *     this is the maximum sum of the sizes of the entries in this cache.
+     */
+    public LruCache(int maxSize) {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        this.maxSize = maxSize;
+        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+    }
+
+    /**
+     * Returns the value for {@code key} if it exists in the cache or can be
+     * created by {@code #create}. If a value was returned, it is moved to the
+     * head of the queue. This returns null if a value is not cached and cannot
+     * be created.
+     */
+    public final V get(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V mapValue;
+        synchronized (this) {
+            mapValue = map.get(key);
+            if (mapValue != null) {
+                hitCount++;
+                return mapValue;
+            }
+            missCount++;
+        }
+
+        /*
+         * Attempt to create a value. This may take a long time, and the map
+         * may be different when create() returns. If a conflicting value was
+         * added to the map while create() was working, we leave that value in
+         * the map and release the created value.
+         */
+
+        V createdValue = create(key);
+        if (createdValue == null) {
+            return null;
+        }
+
+        synchronized (this) {
+            createCount++;
+            mapValue = map.put(key, createdValue);
+
+            if (mapValue != null) {
+                // There was a conflict so undo that last put
+                map.put(key, mapValue);
+            } else {
+                size += safeSizeOf(key, createdValue);
+            }
+        }
+
+        if (mapValue != null) {
+            entryRemoved(false, key, createdValue, mapValue);
+            return mapValue;
+        } else {
+            trimToSize(maxSize);
+            return createdValue;
+        }
+    }
+
+    /**
+     * Caches {@code value} for {@code key}. The value is moved to the head of
+     * the queue.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V put(K key, V value) {
+        if (key == null || value == null) {
+            throw new NullPointerException("key == null || value == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            putCount++;
+            size += safeSizeOf(key, value);
+            previous = map.put(key, value);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, value);
+        }
+
+        trimToSize(maxSize);
+        return previous;
+    }
+
+    /**
+     * @param maxSize the maximum size of the cache before returning. May be -1
+     *     to evict even 0-sized elements.
+     */
+    private void trimToSize(int maxSize) {
+        while (true) {
+            K key;
+            V value;
+            synchronized (this) {
+                if (size < 0 || (map.isEmpty() && size != 0)) {
+                    throw new IllegalStateException(getClass().getName()
+                            + ".sizeOf() is reporting inconsistent results!");
+                }
+
+                if (size <= maxSize || map.isEmpty()) {
+                    break;
+                }
+
+                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
+                key = toEvict.getKey();
+                value = toEvict.getValue();
+                map.remove(key);
+                size -= safeSizeOf(key, value);
+                evictionCount++;
+            }
+
+            entryRemoved(true, key, value, null);
+        }
+    }
+
+    /**
+     * Removes the entry for {@code key} if it exists.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V remove(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            previous = map.remove(key);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, null);
+        }
+
+        return previous;
+    }
+
+    /**
+     * Called for entries that have been evicted or removed. This method is
+     * invoked when a value is evicted to make space, removed by a call to
+     * {@link #remove}, or replaced by a call to {@link #put}. The default
+     * implementation does nothing.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * @param evicted true if the entry is being removed to make space, false
+     *     if the removal was caused by a {@link #put} or {@link #remove}.
+     * @param newValue the new value for {@code key}, if it exists. If non-null,
+     *     this removal was caused by a {@link #put}. Otherwise it was caused by
+     *     an eviction or a {@link #remove}.
+     */
+    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
+
+    /**
+     * Called after a cache miss to compute a value for the corresponding key.
+     * Returns the computed value or null if no value can be computed. The
+     * default implementation returns null.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * <p>If a value for {@code key} exists in the cache when this method
+     * returns, the created value will be released with {@link #entryRemoved}
+     * and discarded. This can occur when multiple threads request the same key
+     * at the same time (causing multiple values to be created), or when one
+     * thread calls {@link #put} while another is creating a value for the same
+     * key.
+     */
+    protected V create(K key) {
+        return null;
+    }
+
+    private int safeSizeOf(K key, V value) {
+        int result = sizeOf(key, value);
+        if (result < 0) {
+            throw new IllegalStateException("Negative size: " + key + "=" + value);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the size of the entry for {@code key} and {@code value} in
+     * user-defined units.  The default implementation returns 1 so that size
+     * is the number of entries and max size is the maximum number of entries.
+     *
+     * <p>An entry's size must not change while it is in the cache.
+     */
+    protected int sizeOf(K key, V value) {
+        return 1;
+    }
+
+    /**
+     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+     */
+    public final void evictAll() {
+        trimToSize(-1); // -1 will evict 0-sized elements
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the number
+     * of entries in the cache. For all other caches, this returns the sum of
+     * the sizes of the entries in this cache.
+     */
+    public synchronized final int size() {
+        return size;
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the maximum
+     * number of entries in the cache. For all other caches, this returns the
+     * maximum sum of the sizes of the entries in this cache.
+     */
+    public synchronized final int maxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned a value.
+     */
+    public synchronized final int hitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned null or required a new
+     * value to be created.
+     */
+    public synchronized final int missCount() {
+        return missCount;
+    }
+
+    /**
+     * Returns the number of times {@link #create(Object)} returned a value.
+     */
+    public synchronized final int createCount() {
+        return createCount;
+    }
+
+    /**
+     * Returns the number of times {@link #put} was called.
+     */
+    public synchronized final int putCount() {
+        return putCount;
+    }
+
+    /**
+     * Returns the number of values that have been evicted.
+     */
+    public synchronized final int evictionCount() {
+        return evictionCount;
+    }
+
+    /**
+     * Returns a copy of the current contents of the cache, ordered from least
+     * recently accessed to most recently accessed.
+     */
+    public synchronized final Map<K, V> snapshot() {
+        return new LinkedHashMap<K, V>(map);
+    }
+
+    @Override public synchronized final String toString() {
+        int accesses = hitCount + missCount;
+        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+                maxSize, hitCount, missCount, hitPercent);
+    }
+}
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6581,16 +6581,20 @@ var ActivityObserver = {
     Services.obs.addObserver(this, "application-background", false);
     Services.obs.addObserver(this, "application-foreground", false);
   },
 
   observe: function ao_observe(aSubject, aTopic, aData) {
     let isForeground = false
     switch (aTopic) {
       case "application-background" :
+        let doc = BrowserApp.selectedTab.browser.contentDocument;
+        if (doc.mozFullScreen) {
+          doc.mozCancelFullScreen();
+        }
         isForeground = false;
         break;
       case "application-foreground" :
         isForeground = true;
         break;
     }
 
     let tab = BrowserApp.selectedTab;
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -47,30 +47,21 @@ SessionStore.prototype = {
   _interval: 10000,
   _maxTabsUndo: 1,
   _shouldRestore: false,
 
   init: function ss_init() {
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
-    this._sessionCache = this._sessionFile.clone();
     this._sessionFile.append("sessionstore.js");
     this._sessionFileBackup.append("sessionstore.bak");
-    this._sessionCache.append("sessionstoreCache");
 
     this._loadState = STATE_STOPPED;
 
-    try {
-      if (!this._sessionCache.exists() || !this._sessionCache.isDirectory())
-        this._sessionCache.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
-    } catch (ex) {
-      Cu.reportError(ex); // file was write-locked?
-    }
-
     this._interval = Services.prefs.getIntPref("browser.sessionstore.interval");
     this._maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
 
     // Do we need to restore session just this once, in case of a restart?
     if (this._sessionFileBackup.exists() && Services.prefs.getBoolPref("browser.sessionstore.resume_session_once")) {
       Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", false);
       this._shouldRestore = true;
     }
@@ -83,46 +74,16 @@ SessionStore.prototype = {
       } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
     }
     if (this._sessionFileBackup.exists()) {
       try {
         this._sessionFileBackup.remove(false);
       } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
     }
 
-    this._clearCache();
-  },
-
-  _clearCache: function ss_clearCache() {
-    // First, let's get a list of files we think should be active
-    let activeFiles = [];
-    this._forEachBrowserWindow(function(aWindow) {
-      let tabs = aWindow.BrowserApp.tabs;
-      for (let i = 0; i < tabs.length; i++) {
-        let browser = tabs[i].browser;
-        if (browser.__SS_extdata && "thumbnail" in browser.__SS_extdata)
-          activeFiles.push(browser.__SS_extdata.thumbnail);
-      }
-    });
-
-    // Now, let's find the stale files in the cache folder
-    let staleFiles = [];
-    let cacheFiles = this._sessionCache.directoryEntries;
-    while (cacheFiles.hasMoreElements()) {
-      let file = cacheFiles.getNext().QueryInterface(Ci.nsILocalFile);
-      let fileURI = Services.io.newFileURI(file);
-      if (activeFiles.indexOf(fileURI) == -1)
-        staleFiles.push(file);
-    }
-
-    // Remove the stale files in a separate step to keep the enumerator from
-    // messing up if we remove the files as we collect them.
-    staleFiles.forEach(function(aFile) {
-      aFile.remove(false);
-    })
   },
 
   _sendMessageToJava: function (aMsg) {
     let data = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge).handleGeckoMessage(JSON.stringify({ gecko: aMsg }));
     return JSON.parse(data);
   },
 
   observe: function ss_observe(aSubject, aTopic, aData) {
@@ -923,36 +884,16 @@ SessionStore.prototype = {
     let browser = aTab.browser;
     let data = browser.__SS_extdata || {};
     return data[aKey] || "";
   },
 
   setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
     let browser = aTab.browser;
 
-    // Thumbnails are actually stored in the cache, so do the save and update the URI
-    if (aKey == "thumbnail") {
-      let file = this._sessionCache.clone();
-      file.append("thumbnail-" + browser.contentWindowId);
-      file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
-
-      let source = Services.io.newURI(aStringValue, "UTF8", null);
-      let target = Services.io.newFileURI(file)
-
-      let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
-      persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
-      let privacyContext = browser.contentWindow
-                                  .QueryInterface(Ci.nsIInterfaceRequestor)
-                                  .getInterface(Ci.nsIWebNavigation)
-                                  .QueryInterface(Ci.nsILoadContext);
-      persist.saveURI(source, null, null, null, null, file, privacyContext);
-
-      aStringValue = target.spec;
-    }
-
     if (!browser.__SS_extdata)
       browser.__SS_extdata = {};
     browser.__SS_extdata[aKey] = aStringValue;
     this.saveStateDelayed();
   },
 
   deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
     let browser = aTab.browser;
@@ -973,17 +914,16 @@ SessionStore.prototype = {
       if (!self._restoreWindow(data)) {
         throw "Could not restore window";
       }
 
       notifyObservers();
     }
 
     function notifyObservers(aMessage) {
-      self._clearCache();
       Services.obs.notifyObservers(null, "sessionstore-windows-restored", aMessage || "");
     }
 
     try {
       if (!aRestoringOOM && !this._shouldRestore) {
         // If we're here, it means we're restoring from a crash (not an OOM
         // kill). Check prefs and other conditions to make sure we want to
         // continue with the restore.
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3624,16 +3624,20 @@ pref("webgl.prefer-native-gl", false);
 pref("webgl.min_capability_mode", false);
 pref("webgl.disable-extensions", false);
 pref("webgl.msaa-level", 2);
 pref("webgl.msaa-force", false);
 pref("webgl.prefer-16bpp", false);
 pref("webgl.default-no-alpha", false);
 pref("webgl.force-layers-readback", false);
 
+// Stagefright prefs
+pref("stagefright.force-enabled", false);
+pref("stagefright.disabled", false);
+
 #ifdef XP_WIN
 // The default TCP send window on Windows is too small, and autotuning only occurs on receive
 pref("network.tcp.sendbuffer", 131072);
 #endif
 
 // Asynchonous video compositing using the ImageBridge IPDL protocol.
 // requires off-main-thread compositing.
 pref("layers.async-video.enabled",false);
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -400,16 +400,21 @@ nsHttpHandler::IsAcceptableEncoding(cons
 
     // HTTP 1.1 allows servers to send x-gzip and x-compress instead
     // of gzip and compress, for example.  So, we'll always strip off
     // an "x-" prefix before matching the encoding to one we claim
     // to accept.
     if (!PL_strncasecmp(enc, "x-", 2))
         enc += 2;
 
+    // gzip and deflate are inherently acceptable in modern HTTP - always
+    // process them if a stream converter can also be found.
+    if (!PL_strcasecmp(enc, "gzip") || !PL_strcasecmp(enc, "deflate"))
+        return true;
+
     return nsHttp::FindToken(mAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr;
 }
 
 nsresult
 nsHttpHandler::GetStreamConverterService(nsIStreamConverterService **result)
 {
     if (!mStreamConvSvc) {
         nsresult rv;
--- a/testing/marionette/marionette-actors.js
+++ b/testing/marionette/marionette-actors.js
@@ -684,17 +684,19 @@ MarionetteDriverActor.prototype = {
     if (this.context == "content") {
       this.sendAsync("executeScript", {value: aRequest.value,
                                        args: aRequest.args,
                                        newSandbox:aRequest.newSandbox});
       return;
     }
 
     let curWindow = this.getCurrentWindow();
-    let marionette = new Marionette(this, curWindow, "chrome", this.marionetteLog, this.marionettePerf);
+    let marionette = new Marionette(this, curWindow, "chrome",
+                                    this.marionetteLog, this.marionettePerf,
+                                    this.scriptTimeout);
     let _chromeSandbox = this.createExecuteSandbox(curWindow, marionette, aRequest.args, aRequest.specialPowers);
     if (!_chromeSandbox)
       return;
 
     try {
       _chromeSandbox.finish = function chromeSandbox_finish() {
         return marionette.generate_results();
       };
@@ -794,17 +796,18 @@ MarionetteDriverActor.prototype = {
                                             newSandbox: aRequest.newSandbox});
       return;
     }
 
     let curWindow = this.getCurrentWindow();
     let original_onerror = curWindow.onerror;
     let that = this;
     let marionette = new Marionette(this, curWindow, "chrome",
-                                    this.marionetteLog, this.marionettePerf);
+                                    this.marionetteLog, this.marionettePerf,
+                                    this.scriptTimeout);
     marionette.command_id = this.command_id;
 
     function chromeAsyncReturnFunc(value, status) {
       if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
         value = "Emulator callback still pending when finish() called";
         status = 500;
         that._emu_cbs = null;
       }
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -259,17 +259,19 @@ function createExecuteContentSandbox(aWi
   let sandbox = new Cu.Sandbox(aWindow);
   sandbox.global = sandbox;
   sandbox.window = aWindow;
   sandbox.document = sandbox.window.document;
   sandbox.navigator = sandbox.window.navigator;
   sandbox.__proto__ = sandbox.window;
   sandbox.testUtils = utils;
 
-  let marionette = new Marionette(this, aWindow, "content", marionetteLogObj, marionettePerf);
+  let marionette = new Marionette(this, aWindow, "content",
+                                  marionetteLogObj, marionettePerf,
+                                  marionetteTimeout);
   sandbox.marionette = marionette;
   marionette.exports.forEach(function(fn) {
     try {
       sandbox[fn] = marionette[fn].bind(marionette);
     }
     catch(e) {
       sandbox[fn] = marionette[fn];
     }
@@ -440,17 +442,16 @@ function executeWithCallback(msg, timeou
   // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
   // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
   // However Selenium code returns 28, see
   // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
   // We'll stay compatible with the Selenium code.
   asyncTestTimeoutId = curWindow.setTimeout(function() {
     sandbox.asyncComplete('timed out', 28);
   }, marionetteTimeout);
-  sandbox.marionette.timeout = marionetteTimeout;
 
   curWindow.addEventListener('error', function win__onerror(evt) {
     curWindow.removeEventListener('error', win__onerror, true);
     sandbox.asyncComplete(evt, 17);
     return true;
   }, true);
 
   let scriptSrc;
--- a/testing/marionette/marionette-simpletest.js
+++ b/testing/marionette/marionette-simpletest.js
@@ -1,23 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 /*
  * The Marionette object, passed to the script context.
  */
 
-function Marionette(scope, window, context, logObj, perfData) {
+function Marionette(scope, window, context, logObj, perfData, timeout) {
   this.scope = scope;
   this.window = window;
   this.tests = [];
   this.logObj = logObj;
   this.perfData = perfData;
   this.context = context;
-  this.timeout = 0;
+  this.timeout = timeout;
   this.TEST_UNEXPECTED_FAIL = "TEST-UNEXPECTED-FAIL";
   this.TEST_PASS = "TEST-PASS";
   this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
 }
 
 Marionette.prototype = {
   exports: ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor',
             'runEmulatorCmd', 'addPerfData', 'getPerfData', 'TEST_PASS',
@@ -139,24 +139,25 @@ Marionette.prototype = {
       return ostring;
   },
 
   waitFor: function test_waitFor(callback, test, timeout) {
       if (test()) {
           callback();
           return;
       }
-      timeout = timeout || Date.now();
-      if (Date.now() - timeout > this.timeout) {
+      var now = Date.now();
+      var deadline = now + (typeof(timeout) == "undefined" ? this.timeout : timeout);
+      if (deadline <= now) {
         dump("waitFor timeout: " + test.toString() + "\n");
         // the script will timeout here, so no need to raise a separate
         // timeout exception
         return;
       }
-      this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, timeout);
+      this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, deadline - now);
   },
 
   runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
     this.scope.runEmulatorCmd(cmd, callback);
   },
 
 };
 
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -201,23 +201,21 @@ function _parseModifiers(aEvent)
  *
  * aEvent is an object which may contain the properties:
  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
  *
  * If the type is specified, an mouse event of that type is fired. Otherwise,
  * a mousedown followed by a mouse up is performed.
  *
  * aWindow is optional, and defaults to the current window object.
- *
- * Returns whether the event had preventDefault() called on it.
  */
 function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
-  return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
+  synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
        aEvent, aWindow);
 }
 function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
   synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
        aEvent, aWindow);
 }
@@ -231,33 +229,30 @@ function synthesizeTouch(aTarget, aOffse
  * If the type is specified, an mouse event of that type is fired. Otherwise,
  * a mousedown followed by a mouse up is performed.
  *
  * aWindow is optional, and defaults to the current window object.
  */
 function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
 {
   var utils = _getDOMWindowUtils(aWindow);
-  var defaultPrevented = false;
 
   if (utils) {
     var button = aEvent.button || 0;
     var clickCount = aEvent.clickCount || 1;
     var modifiers = _parseModifiers(aEvent);
 
     if (("type" in aEvent) && aEvent.type) {
-      defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
+      utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
     }
     else {
       utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
       utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
     }
   }
-
-  return defaultPrevented;
 }
 function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
 {
   var utils = _getDOMWindowUtils(aWindow);
 
   if (utils) {
     var id = aEvent.id || 0;
     var rx = aEvent.rx || 1;
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -259,28 +259,58 @@ xpcshell-tests:
           $(SYMBOLS_PATH) \
 	  $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \
 	  $(LIBXUL_DIST)/bin/xpcshell
 
 REMOTE_XPCSHELL = \
 	rm -f ./$@.log && \
 	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
 	  -I$(topsrcdir)/build \
-	  -I$(topsrcdir)/build/mobile \
 	  -I$(topsrcdir)/testing/mozbase/mozdevice/mozdevice \
 	  $(topsrcdir)/testing/xpcshell/remotexpcshelltests.py \
 	  --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \
 	  --build-info-json=$(DEPTH)/mozinfo.json \
 	  --no-logfiles \
 	  --dm_trans=$(DM_TRANS) \
 	  --deviceIP=${TEST_DEVICE} \
 	  --objdir=$(DEPTH) \
 	  $(SYMBOLS_PATH) \
 	  $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS)
 
+B2G_XPCSHELL = \
+	rm -f ./@.log && \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+	  -I$(topsrcdir)/build \
+	  $(topsrcdir)/testing/xpcshell/runtestsb2g.py \
+	  --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \
+	  --build-info-json=$(DEPTH)/mozinfo.json \
+	  --no-logfiles \
+	  --use-device-libs \
+	  --no-clean \
+	  --objdir=$(DEPTH) \
+	  $$EXTRA_XPCSHELL_ARGS \
+	  --b2gpath=${B2G_HOME} \
+	  $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS)
+
+xpcshell-tests-b2g: ADB_PATH?=$(shell which adb)
+xpcshell-tests-b2g:
+	@if [ "${B2G_HOME}" = "" ]; then \
+		echo "Please set the B2G_HOME variable"; exit 1; \
+	elif [ ! -f "${ADB_PATH}" ]; then \
+		echo "Please set the ADB_PATH variable"; exit 1; \
+	elif [ "${EMULATOR}" != "" ]; then \
+		EXTRA_XPCSHELL_ARGS=--emulator=${EMULATOR}; \
+		$(call B2G_XPCSHELL); \
+		exit 0; \
+	else \
+		EXTRA_XPCSHELL_ARGS=--address=localhost:2828; \
+		$(call B2G_XPCSHELL); \
+		exit 0; \
+	fi
+
 xpcshell-tests-remote: DM_TRANS?=adb
 xpcshell-tests-remote:
 	@if [ "${TEST_DEVICE}" != "" -o "$(DM_TRANS)" = "adb" ]; \
           then $(call REMOTE_XPCSHELL); $(CHECK_TEST_ERROR); \
         else \
           echo "please prepare your host with environment variables for TEST_DEVICE"; \
         fi
 
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -3,17 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import re, sys, os
 import subprocess
 import runxpcshelltests as xpcshell
 from automationutils import *
-import devicemanager, devicemanagerADB, devicemanagerSUT
+from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT
 
 # A specialization of XPCShellTests that runs tests on an Android device
 # via devicemanager.
 class XPCShellRemote(xpcshell.XPCShellTests, object):
 
     def __init__(self, devmgr, options, args):
         xpcshell.XPCShellTests.__init__(self)
         self.options = options
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -4,17 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import sys
 import os
 import traceback
 from remotexpcshelltests import XPCShellRemote, RemoteXPCShellOptions
 from automationutils import *
-import devicemanagerADB
+from mozdevice import devicemanagerADB
 
 DEVICE_TEST_ROOT = '/data/local/tests'
 
 sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))))
 
 from marionette import Marionette
 
 
--- a/toolkit/content/WindowDraggingUtils.jsm
+++ b/toolkit/content/WindowDraggingUtils.jsm
@@ -1,24 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifdef XP_WIN
-#define USE_HITTEST
-#elifdef MOZ_WIDGET_COCOA
-#define USE_HITTEST
-#endif
-
 this.EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];
 
 this.WindowDraggingElement = function WindowDraggingElement(elem) {
   this._elem = elem;
   this._window = elem.ownerDocument.defaultView;
-#ifdef USE_HITTEST
+#ifdef XP_WIN
   if (!this.isPanel())
     this._elem.addEventListener("MozMouseHittest", this, false);
   else
 #endif
   this._elem.addEventListener("mousedown", this, false);
 };
 
 WindowDraggingElement.prototype = {
@@ -55,17 +49,17 @@ WindowDraggingElement.prototype = {
     return true;
   },
   isPanel : function() {
     return this._elem instanceof Components.interfaces.nsIDOMXULElement &&
            this._elem.localName == "panel";
   },
   handleEvent: function(aEvent) {
     let isPanel = this.isPanel();
-#ifdef USE_HITTEST
+#ifdef XP_WIN
     if (!isPanel) {
       if (this.shouldDrag(aEvent))
         aEvent.preventDefault();
       return;
     }
 #endif
 
     switch (aEvent.type) {
--- a/toolkit/content/tests/chrome/window_titlebar.xul
+++ b/toolkit/content/tests/chrome/window_titlebar.xul
@@ -47,16 +47,17 @@
       <label id="panellabelfloating" value="Titlebar"/>
     </titlebar>
   </panel>
 
   <button id="button" label="OK"/>
   <statusbar id="statusbar">
     <statusbarpanel>
       <label id="statuslabel" value="Status"/>
+      <label id="statuslabelnodrag" value="No Drag" onmousedown="event.preventDefault()"/>
     </statusbarpanel>
   </statusbar>
 
 <script>
 <![CDATA[
 
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 
@@ -100,28 +101,36 @@ function waitForWindowMove(element, x, y
   }
 }
 
 function test_titlebar()
 {
   var titlebar = document.getElementById("titlebar");
   var label = document.getElementById("label");
 
-  // On Mac, the window can also be moved with the statusbar, but this works
-  // via the MozMouseHittest event, not via mouse events.
+  // on Mac, the window can also be moved with the statusbar
   if (navigator.platform.indexOf("Mac") >= 0) {
-    var preventDefaulted;
-
     var statuslabel = document.getElementById("statuslabel");
-    preventDefaulted = synthesizeMouse(statuslabel, 2, 2, { type: "MozMouseHittest" });
-    SimpleTest.ok(preventDefaulted, "MozMouseHittest should have been defaultPrevented over statusbar");
+    var statuslabelnodrag = document.getElementById("statuslabelnodrag");
+
+    origoldx = window.screenX;
+    origoldy = window.screenY;
 
-    var button = document.getElementById("button");
-    preventDefaulted = synthesizeMouse(button, 2, 2, { type: "MozMouseHittest" });
-    SimpleTest.ok(!preventDefaulted, "MozMouseHittest should NOT have been defaultPrevented over button");
+    synthesizeMouse(statuslabel, 2, 2, { type: "mousedown" });
+    synthesizeMouse(statuslabel, 22, 22, { type: "mousemove" });
+    SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar horizontal");
+    SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar vertical");
+    synthesizeMouse(statuslabel, 22, 22, { type: "mouseup" });
+
+    // event was cancelled so the drag should not have occurred
+    synthesizeMouse(statuslabelnodrag, 2, 2, { type: "mousedown" });
+    synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mousemove" });
+    SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar cancelled mousedown horizontal");
+    SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar cancelled mousedown vertical");
+    synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mouseup" });
   }
 
   origoldx = window.screenX;
   origoldy = window.screenY;
 
   var mousedownListener = function (event) mouseDownTarget = event.originalTarget;
   window.addEventListener("mousedown", mousedownListener, false);
   synthesizeMouse(label, 2, 2, { type: "mousedown" });
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -102,17 +102,16 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jNotifyIMEEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEEnabled", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V");
     jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V");
     jAcknowledgeEventSync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "acknowledgeEventSync", "()V");
 
     jEnableLocation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableLocation", "(Z)V");
     jEnableLocationHighAccuracy = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableLocationHighAccuracy", "(Z)V");
     jEnableSensor = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableSensor", "(I)V");
     jDisableSensor = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableSensor", "(I)V");
-    jReturnIMEQueryResult = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "returnIMEQueryResult", "(Ljava/lang/String;II)V");
     jScheduleRestart = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scheduleRestart", "()V");
     jNotifyXreExit = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "onXreExit", "()V");
     jGetHandlersForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForMimeType", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
     jGetHandlersForURL = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForURL", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
     jOpenUriExternal = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "openUriExternal", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");
     jGetMimeTypeFromExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMimeTypeFromExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jGetExtensionFromMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getExtensionFromMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
     jMoveTaskToBack = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "moveTaskToBack", "()V");
@@ -396,35 +395,16 @@ AndroidBridge::DisableSensor(int aSensor
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
     AutoLocalJNIFrame jniFrame(env, 0);
     env->CallStaticVoidMethod(mGeckoAppShellClass, jDisableSensor, aSensorType);
 }
 
 void
-AndroidBridge::ReturnIMEQueryResult(const PRUnichar *aResult, uint32_t aLen,
-                                    int aSelStart, int aSelLen)
-{
-    ALOG_BRIDGE("AndroidBridge::ReturnIMEQueryResult");
-
-    JNIEnv *env = GetJNIEnv();
-    if (!env)
-        return;
-
-    AutoLocalJNIFrame jniFrame(env);
-    jvalue args[3];
-    args[0].l = NewJavaString(&jniFrame, aResult, aLen);
-    args[1].i = aSelStart;
-    args[2].i = aSelLen;
-    env->CallStaticVoidMethodA(mGeckoAppShellClass,
-                               jReturnIMEQueryResult, args);
-}
-
-void
 AndroidBridge::ScheduleRestart()
 {
     ALOG_BRIDGE("scheduling reboot");
 
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -95,17 +95,17 @@ protected:
     virtual ~nsFilePickerCallback() {}
 };
 
 class AndroidBridge
 {
 public:
     enum {
         NOTIFY_IME_RESETINPUTSTATE = 0,
-        NOTIFY_IME_SETOPENSTATE = 1,
+        NOTIFY_IME_REPLY_EVENT = 1,
         NOTIFY_IME_CANCELCOMPOSITION = 2,
         NOTIFY_IME_FOCUSCHANGE = 3
     };
 
     enum {
         LAYER_CLIENT_TYPE_NONE = 0,
         LAYER_CLIENT_TYPE_GL = 2            // AndroidGeckoGLLayerClient
     };
@@ -166,18 +166,16 @@ public:
 
     void EnableLocation(bool aEnable);
     void EnableLocationHighAccuracy(bool aEnable);
 
     void EnableSensor(int aSensorType);
 
     void DisableSensor(int aSensorType);
 
-    void ReturnIMEQueryResult(const PRUnichar *aResult, uint32_t aLen, int aSelStart, int aSelLen);
-
     void NotifyXreExit();
 
     void ScheduleRestart();
 
     void SetLayerClient(JNIEnv* env, jobject jobj);
     AndroidGeckoLayerClient &GetLayerClient() { return *mLayerClient; }
 
     void SetSurfaceView(jobject jobj);
@@ -403,17 +401,16 @@ protected:
     jmethodID jNotifyIME;
     jmethodID jNotifyIMEEnabled;
     jmethodID jNotifyIMEChange;
     jmethodID jAcknowledgeEventSync;
     jmethodID jEnableLocation;
     jmethodID jEnableLocationHighAccuracy;
     jmethodID jEnableSensor;
     jmethodID jDisableSensor;
-    jmethodID jReturnIMEQueryResult;
     jmethodID jNotifyAppShellReady;
     jmethodID jNotifyXreExit;
     jmethodID jScheduleRestart;
     jmethodID jGetOutstandingDrawEvents;
     jmethodID jGetHandlersForMimeType;
     jmethodID jGetHandlersForURL;
     jmethodID jOpenUriExternal;
     jmethodID jGetMimeTypeFromExtensions;
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -28,18 +28,19 @@ jfieldID AndroidGeckoEvent::jNativeWindo
 jfieldID AndroidGeckoEvent::jCharactersField = 0;
 jfieldID AndroidGeckoEvent::jCharactersExtraField = 0;
 jfieldID AndroidGeckoEvent::jKeyCodeField = 0;
 jfieldID AndroidGeckoEvent::jMetaStateField = 0;
 jfieldID AndroidGeckoEvent::jDomKeyLocationField = 0;
 jfieldID AndroidGeckoEvent::jFlagsField = 0;
 jfieldID AndroidGeckoEvent::jUnicodeCharField = 0;
 jfieldID AndroidGeckoEvent::jRepeatCountField = 0;
-jfieldID AndroidGeckoEvent::jOffsetField = 0;
 jfieldID AndroidGeckoEvent::jCountField = 0;
+jfieldID AndroidGeckoEvent::jStartField = 0;
+jfieldID AndroidGeckoEvent::jEndField = 0;
 jfieldID AndroidGeckoEvent::jPointerIndexField = 0;
 jfieldID AndroidGeckoEvent::jRangeTypeField = 0;
 jfieldID AndroidGeckoEvent::jRangeStylesField = 0;
 jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
 jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 jfieldID AndroidGeckoEvent::jBandwidthField = 0;
 jfieldID AndroidGeckoEvent::jCanBeMeteredField = 0;
@@ -222,18 +223,19 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jCharactersField = getField("mCharacters", "Ljava/lang/String;");
     jCharactersExtraField = getField("mCharactersExtra", "Ljava/lang/String;");
     jKeyCodeField = getField("mKeyCode", "I");
     jMetaStateField = getField("mMetaState", "I");
     jDomKeyLocationField = getField("mDomKeyLocation", "I");
     jFlagsField = getField("mFlags", "I");
     jUnicodeCharField = getField("mUnicodeChar", "I");
     jRepeatCountField = getField("mRepeatCount", "I");
-    jOffsetField = getField("mOffset", "I");
     jCountField = getField("mCount", "I");
+    jStartField = getField("mStart", "I");
+    jEndField = getField("mEnd", "I");
     jPointerIndexField = getField("mPointerIndex", "I");
     jRangeTypeField = getField("mRangeType", "I");
     jRangeStylesField = getField("mRangeStyles", "I");
     jRangeForeColorField = getField("mRangeForeColor", "I");
     jRangeBackColorField = getField("mRangeBackColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
     jBandwidthField = getField("mBandwidth", "D");
     jCanBeMeteredField = getField("mCanBeMetered", "Z");
@@ -559,24 +561,23 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
             ReadFloatArray(mOrientations, jenv, jOrientations, mCount);
             ReadFloatArray(mPressures, jenv, jPressures, mCount);
             ReadPointArray(mPoints, jenv, jPoints, mCount);
             ReadIntArray(mPointIndicies, jenv, jPointIndicies, mCount);
 
             break;
 
         case IME_EVENT:
-            if (mAction == IME_GET_TEXT || mAction == IME_SET_SELECTION) {
-                mOffset = jenv->GetIntField(jobj, jOffsetField);
-                mCount = jenv->GetIntField(jobj, jCountField);
-            } else if (mAction == IME_SET_TEXT || mAction == IME_ADD_RANGE) {
-                if (mAction == IME_SET_TEXT)
-                    ReadCharactersField(jenv);
-                mOffset = jenv->GetIntField(jobj, jOffsetField);
-                mCount = jenv->GetIntField(jobj, jCountField);
+            mStart = jenv->GetIntField(jobj, jStartField);
+            mEnd = jenv->GetIntField(jobj, jEndField);
+
+            if (mAction == IME_REPLACE_TEXT) {
+                ReadCharactersField(jenv);
+            } else if (mAction == IME_UPDATE_COMPOSITION ||
+                    mAction == IME_ADD_COMPOSITION_RANGE) {
                 mRangeType = jenv->GetIntField(jobj, jRangeTypeField);
                 mRangeStyles = jenv->GetIntField(jobj, jRangeStylesField);
                 mRangeForeColor =
                     jenv->GetIntField(jobj, jRangeForeColorField);
                 mRangeBackColor =
                     jenv->GetIntField(jobj, jRangeBackColorField);
             }
             break;
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -671,18 +671,19 @@ public:
     int DomKeyLocation() { return mDomKeyLocation; }
     bool IsAltPressed() const { return (mMetaState & AndroidKeyEvent::META_ALT_MASK) != 0; }
     bool IsShiftPressed() const { return (mMetaState & AndroidKeyEvent::META_SHIFT_MASK) != 0; }
     bool IsCtrlPressed() const { return (mMetaState & AndroidKeyEvent::META_CTRL_MASK) != 0; }
     bool IsMetaPressed() const { return (mMetaState & AndroidKeyEvent::META_META_MASK) != 0; }
     int Flags() { return mFlags; }
     int UnicodeChar() { return mUnicodeChar; }
     int RepeatCount() const { return mRepeatCount; }
-    int Offset() { return mOffset; }
     int Count() { return mCount; }
+    int Start() { return mStart; }
+    int End() { return mEnd; }
     int PointerIndex() { return mPointerIndex; }
     int RangeType() { return mRangeType; }
     int RangeStyles() { return mRangeStyles; }
     int RangeForeColor() { return mRangeForeColor; }
     int RangeBackColor() { return mRangeBackColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     double Bandwidth() { return mBandwidth; }
     bool CanBeMetered() { return mCanBeMetered; }
@@ -698,17 +699,18 @@ protected:
     nsTArray<int> mPointIndicies;
     nsTArray<float> mOrientations;
     nsTArray<float> mPressures;
     nsIntRect mRect;
     int mFlags, mMetaState;
     int mDomKeyLocation;
     int mKeyCode, mUnicodeChar;
     int mRepeatCount;
-    int mOffset, mCount;
+    int mCount;
+    int mStart, mEnd;
     int mRangeType, mRangeStyles;
     int mRangeForeColor, mRangeBackColor;
     double mX, mY, mZ;
     int mPointerIndex;
     nsString mCharacters, mCharactersExtra;
     nsRefPtr<nsGeoPosition> mGeoPosition;
     double mBandwidth;
     bool mCanBeMetered;
@@ -748,18 +750,19 @@ protected:
     static jfieldID jNativeWindowField;
 
     static jfieldID jCharactersField;
     static jfieldID jCharactersExtraField;
     static jfieldID jKeyCodeField;
     static jfieldID jMetaStateField;
     static jfieldID jDomKeyLocationField;
     static jfieldID jFlagsField;
-    static jfieldID jOffsetField;
     static jfieldID jCountField;
+    static jfieldID jStartField;
+    static jfieldID jEndField;
     static jfieldID jPointerIndexField;
     static jfieldID jUnicodeCharField;
     static jfieldID jRepeatCountField;
     static jfieldID jRangeTypeField;
     static jfieldID jRangeStylesField;
     static jfieldID jRangeForeColorField;
     static jfieldID jRangeBackColorField;
     static jfieldID jLocationField;
@@ -802,24 +805,22 @@ public:
         COMPOSITOR_PAUSE = 28,
         COMPOSITOR_RESUME = 29,
         PAINT_LISTEN_START_EVENT = 30,
         NATIVE_GESTURE_EVENT = 31,
         dummy_java_enum_list_end
     };
 
     enum {
-        IME_COMPOSITION_END = 0,
-        IME_COMPOSITION_BEGIN = 1,
-        IME_SET_TEXT = 2,
-        IME_GET_TEXT = 3,
-        IME_DELETE_TEXT = 4,
-        IME_SET_SELECTION = 5,
-        IME_GET_SELECTION = 6,
-        IME_ADD_RANGE = 7
+        IME_SYNCHRONIZE = 0,
+        IME_REPLACE_TEXT = 1,
+        IME_SET_SELECTION = 2,
+        IME_ADD_COMPOSITION_RANGE = 3,
+        IME_UPDATE_COMPOSITION = 4,
+        IME_REMOVE_COMPOSITION = 5
     };
 };
 
 class nsJNIString : public nsString
 {
 public:
     nsJNIString(jstring jstr, JNIEnv *jenv);
 };
--- a/widget/android/GfxInfo.cpp
+++ b/widget/android/GfxInfo.cpp
@@ -117,37 +117,36 @@ GfxInfo::EnsureInitializedFromGfxInfoDat
                                    mRenderer.get(),
                                    mVersion.get());
 
   // Now we append general (non-gfx) device information. The only reason why this code is still here
   // is that this used to be all we had in GfxInfo on Android, and we can't trivially remove it
   // as it's useful information that isn't given anywhere else in about:support of in crash reports.
   // But we should really move this out of GfxInfo.
   if (mozilla::AndroidBridge::Bridge()) {
-    nsAutoString str;
-    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "MODEL", str)) {
-      mAdapterDescription.AppendPrintf(" -- Model: %s",  NS_LossyConvertUTF16toASCII(str).get());
+    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "MODEL", mModel)) {
+      mAdapterDescription.AppendPrintf(" -- Model: %s",  NS_LossyConvertUTF16toASCII(mModel).get());
     }
 
-    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "PRODUCT", str)) {
-      mAdapterDescription.AppendPrintf(", Product: %s", NS_LossyConvertUTF16toASCII(str).get());
+    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "PRODUCT", mProduct)) {
+      mAdapterDescription.AppendPrintf(", Product: %s", NS_LossyConvertUTF16toASCII(mProduct).get());
     }
 
-    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "MANUFACTURER", str)) {
-      mAdapterDescription.AppendPrintf(", Manufacturer: %s", NS_LossyConvertUTF16toASCII(str).get());
+    if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "MANUFACTURER", mManufacturer)) {
+      mAdapterDescription.AppendPrintf(", Manufacturer: %s", NS_LossyConvertUTF16toASCII(mManufacturer).get());
     }
 
-    int32_t version; // the HARDWARE field isn't available on Android SDK < 8
-    if (!mozilla::AndroidBridge::Bridge()->GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &version))
-      version = 0;
+    int32_t signedVersion;
+    if (!mozilla::AndroidBridge::Bridge()->GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &signedVersion))
+      signedVersion = 0;
+    mOSVersion = signedVersion;
 
-    if (version >= 8 && mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "HARDWARE", str)) {
-      if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "HARDWARE", str)) {
-        mAdapterDescription.AppendPrintf(", Hardware: %s", NS_LossyConvertUTF16toASCII(str).get());
-      }
+    // the HARDWARE field isn't available on Android SDK < 8
+    if (mOSVersion >= 8 && mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "HARDWARE", mHardware)) {
+      mAdapterDescription.AppendPrintf(", Hardware: %s", NS_LossyConvertUTF16toASCII(mHardware).get());
     }
   }
 
   AddCrashReportAnnotations();
 }
 
 /* readonly attribute DOMString adapterDescription; */
 NS_IMETHODIMP
@@ -304,17 +303,17 @@ GfxInfo::GetFeatureStatusImpl(int32_t aF
                               int32_t *aStatus, 
                               nsAString & aSuggestedDriverVersion,
                               const nsTArray<GfxDriverInfo>& aDriverInfo, 
                               OperatingSystem* aOS /* = nullptr */)
 {
   NS_ENSURE_ARG_POINTER(aStatus);
   aSuggestedDriverVersion.SetIsVoid(true);
   *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
-  OperatingSystem os = DRIVER_OS_ANDROID;
+  OperatingSystem os = mOS;
   if (aOS)
     *aOS = os;
 
   EnsureInitializedFromGfxInfoData();
 
   if (!mError.IsEmpty()) {
     *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
     return NS_OK;
@@ -325,16 +324,37 @@ GfxInfo::GetFeatureStatusImpl(int32_t aF
     if (aFeature == FEATURE_WEBGL_OPENGL) {
       if (mRenderer.Find("Adreno 200") != -1 ||
           mRenderer.Find("Adreno 205") != -1)
       {
         *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
         return NS_OK;
       }
     }
+
+    if (aFeature == FEATURE_STAGEFRIGHT) {
+      NS_LossyConvertUTF16toASCII cManufacturer(mManufacturer);
+      NS_LossyConvertUTF16toASCII cModel(mModel);
+      if (mOSVersion < 14 /* Before version 4.0 */ )
+      {
+        *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+        return NS_OK;
+      }
+      else if (mOSVersion < 16 /* Before version 4.1 */ )
+      {
+        bool isWhitelisted =
+          cManufacturer.Equals("samsung", nsCaseInsensitiveCStringComparator()) ||
+          cModel.Equals("galaxy nexus", nsCaseInsensitiveCStringComparator()); // some Galaxy Nexus have manufacturer=amazon
+
+        if (!isWhitelisted) {
+          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+          return NS_OK;
+        }
+      }
+    }
   }
 
   return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, &os);
 }
 
 #ifdef DEBUG
 
 // Implement nsIGfxInfoDebug
@@ -361,12 +381,34 @@ NS_IMETHODIMP GfxInfo::SpoofDriverVersio
   EnsureInitializedFromGfxInfoData(); // initialization from GfxInfo data overwrites mVersion
   mVersion = NS_LossyConvertUTF16toASCII(aDriverVersion);
   return NS_OK;
 }
 
 /* void spoofOSVersion (in unsigned long aVersion); */
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
 {
+  EnsureInitializedFromGfxInfoData(); // initialization from GfxInfo data overwrites mOSVersion
+  mOSVersion = aVersion;
   return NS_OK;
 }
 
 #endif
+
+const nsAString& GfxInfo::Model() const
+{
+  return mModel;
+}
+
+const nsAString& GfxInfo::Hardware() const
+{
+  return mHardware;
+}
+
+const nsAString& GfxInfo::Product() const
+{
+  return mProduct;
+}
+
+const nsAString& GfxInfo::Manufacturer() const
+{
+  return mManufacturer;
+}
--- a/widget/android/GfxInfo.h
+++ b/widget/android/GfxInfo.h
@@ -43,21 +43,28 @@ public:
   NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
   NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
   using GfxInfoBase::GetWebGLParameter;
 
   void EnsureInitializedFromGfxInfoData();
 
+  virtual const nsAString& Model() const;
+  virtual const nsAString& Hardware() const;
+  virtual const nsAString& Product() const;
+  virtual const nsAString& Manufacturer() const;
+
 #ifdef DEBUG
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIGFXINFODEBUG
 #endif
 
+  virtual uint32_t OperatingSystemVersion() const { return mOSVersion; }
+
 protected:
 
   virtual nsresult GetFeatureStatusImpl(int32_t aFeature, 
                                         int32_t *aStatus, 
                                         nsAString & aSuggestedDriverVersion, 
                                         const nsTArray<GfxDriverInfo>& aDriverInfo,
                                         OperatingSystem* aOS = nullptr);
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
@@ -71,14 +78,19 @@ private:
   // the GL strings
   nsCString mVendor;
   nsCString mRenderer;
   nsCString mVersion;
   // a possible error message produced by the data source (e.g. if EGL initialization failed)
   nsCString mError;
 
   nsCString mAdapterDescription;
+
+  OperatingSystem mOS;
+  uint32_t mOSVersion;
+
+  nsString mModel, mHardware, mManufacturer, mProduct;
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif /* __mozilla_widget_GfxInfo_h__ */
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -156,17 +156,19 @@ nsWindow::DumpWindows(const nsTArray<nsW
         DumpWindows(w->mChildren, indent+1);
     }
 }
 
 nsWindow::nsWindow() :
     mIsVisible(false),
     mParent(nullptr),
     mFocus(nullptr),
-    mIMEComposing(false)
+    mIMEComposing(false),
+    mIMEMaskSelectionUpdate(false),
+    mIMEMaskTextUpdate(false)
 {
 }
 
 nsWindow::~nsWindow()
 {
     gTopLevelWindows.RemoveElement(this);
     nsWindow *top = FindTopLevel();
     if (top->mFocus == this)
@@ -634,30 +636,28 @@ nsWindow::DispatchEvent(nsGUIEvent *aEve
 nsEventStatus
 nsWindow::DispatchEvent(nsGUIEvent *aEvent)
 {
     if (mWidgetListener) {
         nsEventStatus status = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
 
         switch (aEvent->message) {
         case NS_COMPOSITION_START:
+            MOZ_ASSERT(!mIMEComposing);
             mIMEComposing = true;
             break;
         case NS_COMPOSITION_END:
+            MOZ_ASSERT(mIMEComposing);
             mIMEComposing = false;
+            mIMEComposingText.Truncate();
             break;
         case NS_TEXT_TEXT:
+            MOZ_ASSERT(mIMEComposing);
             mIMEComposingText = static_cast<nsTextEvent*>(aEvent)->theText;
             break;
-        case NS_KEY_PRESS:
-            // Sometimes the text changes after a key press do not generate notifications (see Bug 723810)
-            // Call the corresponding methods explicitly to send those changes back to Java
-            OnIMETextChange(0, 0, 0);
-            OnIMESelectionChange();
-            break;
         }
         return status;
     }
     return nsEventStatus_eIgnore;
 }
 
 NS_IMETHODIMP
 nsWindow::MakeFullScreen(bool aFullScreen)
@@ -1746,16 +1746,17 @@ nsWindow::HandleSpecialKey(AndroidGeckoE
         DispatchEvent(&event);
     }
 }
 
 void
 nsWindow::OnKeyEvent(AndroidGeckoEvent *ae)
 {
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
+    RemoveIMEComposition();
     uint32_t msg;
     switch (ae->Action()) {
     case AndroidKeyEvent::ACTION_DOWN:
         msg = NS_KEY_DOWN;
         break;
     case AndroidKeyEvent::ACTION_UP:
         msg = NS_KEY_UP;
         break;
@@ -1811,191 +1812,248 @@ nsWindow::OnKeyEvent(AndroidGeckoEvent *
 }
 
 #ifdef DEBUG_ANDROID_IME
 #define ALOGIME(args...) ALOG(args)
 #else
 #define ALOGIME(args...)
 #endif
 
-void
-nsWindow::OnIMEAddRange(AndroidGeckoEvent *ae)
+static nscolor
+ConvertAndroidColor(uint32_t c)
 {
-    //ALOGIME("IME: IME_ADD_RANGE");
-    nsTextRange range;
-    range.mStartOffset = ae->Offset();
-    range.mEndOffset = range.mStartOffset + ae->Count();
-    range.mRangeType = ae->RangeType();
-    range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
-    range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
-    range.mRangeStyle.mForegroundColor = NS_RGBA(
-        ((ae->RangeForeColor() >> 16) & 0xff),
-        ((ae->RangeForeColor() >> 8) & 0xff),
-        (ae->RangeForeColor() & 0xff),
-        ((ae->RangeForeColor() >> 24) & 0xff));
-    range.mRangeStyle.mBackgroundColor = NS_RGBA(
-        ((ae->RangeBackColor() >> 16) & 0xff),
-        ((ae->RangeBackColor() >> 8) & 0xff),
-        (ae->RangeBackColor() & 0xff),
-        ((ae->RangeBackColor() >> 24) & 0xff));
-    mIMERanges.AppendElement(range);
-    return;
+    return NS_RGBA((c & 0x000000ff),
+                   (c & 0x0000ff00) >> 8,
+                   (c & 0x00ff0000) >> 16,
+                   (c & 0xff000000) >> 24);
+}
+
+class AutoIMEMask {
+private:
+    bool mOldMask, *mMask;
+public:
+    AutoIMEMask(bool &mask) : mOldMask(mask), mMask(&mask) {
+        mask = true;
+    }
+    ~AutoIMEMask() {
+        *mMask = mOldMask;
+    }
+};
+
+/*
+    Remove the composition but leave the text content as-is
+*/
+void
+nsWindow::RemoveIMEComposition()
+{
+    // Remove composition on Gecko side
+    if (!mIMEComposing)
+        return;
+
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    AutoIMEMask textMask(mIMEMaskTextUpdate);
+
+    nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
+    InitEvent(textEvent, nullptr);
+    textEvent.theText = mIMEComposingText;
+    DispatchEvent(&textEvent);
+
+    nsCompositionEvent event(true, NS_COMPOSITION_END, this);
+    InitEvent(event, nullptr);
+    DispatchEvent(&event);
 }
 
 void
 nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
 {
+    MOZ_ASSERT(!mIMEMaskTextUpdate);
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+    /*
+        Rules for managing IME between Gecko and Java:
+
+        * Gecko controls the text content, and Java shadows the Gecko text
+           through text updates
+        * Java controls the selection, and Gecko shadows the Java selection
+           through set selection events
+        * Java controls the composition, and Gecko shadows the Java
+           composition through update composition events
+    */
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
     switch (ae->Action()) {
-    case AndroidGeckoEvent::IME_COMPOSITION_END:
+    case AndroidGeckoEvent::IME_SYNCHRONIZE:
+        {
+            AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT, 0);
+        }
+        break;
+    case AndroidGeckoEvent::IME_REPLACE_TEXT:
         {
-            ALOGIME("IME: IME_COMPOSITION_END");
-            MOZ_ASSERT(mIMEComposing,
-                       "IME_COMPOSITION_END when we are not composing?!");
+            /*
+                Replace text in Gecko thread from ae->Start() to ae->End()
+                  with the string ae->Characters()
+
+                Selection updates are masked so the result of our temporary
+                  selection event is not passed on to Java
 
-            nsCompositionEvent event(true, NS_COMPOSITION_END, this);
-            InitEvent(event, nullptr);
-            event.data = mIMELastDispatchedComposingText;
-            mIMELastDispatchedComposingText.Truncate();
-            DispatchEvent(&event);
+                Text updates are passed on, so the Java text can shadow the
+                  Gecko text
+            */
+            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+            RemoveIMEComposition();
+            {
+                nsSelectionEvent event(true, NS_SELECTION_SET, this);
+                InitEvent(event, nullptr);
+                event.mOffset = uint32_t(ae->Start());
+                event.mLength = uint32_t(ae->End() - ae->Start());
+                event.mExpandToClusterBoundary = false;
+                DispatchEvent(&event);
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_START, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+            }
+            {
+                nsTextEvent event(true, NS_TEXT_TEXT, this);
+                InitEvent(event, nullptr);
+                event.theText = ae->Characters();
+                DispatchEvent(&event);
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_END, this);
+                InitEvent(event, nullptr);
+                event.data = ae->Characters();
+                DispatchEvent(&event);
+            }
+            AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT, 0);
         }
-        return;
-    case AndroidGeckoEvent::IME_COMPOSITION_BEGIN:
+        break;
+    case AndroidGeckoEvent::IME_SET_SELECTION:
         {
-            ALOGIME("IME: IME_COMPOSITION_BEGIN");
-            MOZ_ASSERT(!mIMEComposing,
-                       "IME_COMPOSITION_BEGIN when we are already composing?!");
+            /*
+                Set Gecko selection to ae->Start() to ae->End()
 
-            mIMELastDispatchedComposingText.Truncate();
-            nsCompositionEvent event(true, NS_COMPOSITION_START, this);
-            InitEvent(event, nullptr);
-            DispatchEvent(&event);
+                Selection updates are masked to prevent Java from being
+                  notified of the new selection
+            */
+            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+            nsSelectionEvent selEvent(true, NS_SELECTION_SET, this);
+            InitEvent(selEvent, nullptr);
+
+            int32_t start = ae->Start(), end = ae->End();
+
+            if (start < 0 || end < 0) {
+                nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+                MOZ_ASSERT(event.mSucceeded && !event.mWasAsync);
+
+                if (start < 0)
+                    start = int32_t(event.GetSelectionStart());
+                if (end < 0)
+                    end = int32_t(event.GetSelectionEnd());
+            }
+
+            selEvent.mOffset = std::min(start, end);
+            selEvent.mLength = std::max(start, end) - selEvent.mOffset;
+            selEvent.mReversed = start > end;
+            selEvent.mExpandToClusterBoundary = false;
+
+            DispatchEvent(&selEvent);
         }
-        return;
-    case AndroidGeckoEvent::IME_ADD_RANGE:
+        break;
+    case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE:
         {
-            NS_ASSERTION(mIMEComposing,
-                         "IME_ADD_RANGE when we are not composing?!");
-            OnIMEAddRange(ae);
+            nsTextRange range;
+            range.mStartOffset = ae->Start();
+            range.mEndOffset = ae->End();
+            range.mRangeType = ae->RangeType();
+            range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
+            range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
+            range.mRangeStyle.mForegroundColor =
+                    ConvertAndroidColor(uint32_t(ae->RangeForeColor()));
+            range.mRangeStyle.mBackgroundColor =
+                    ConvertAndroidColor(uint32_t(ae->RangeBackColor()));
+            mIMERanges.AppendElement(range);
         }
-        return;
-    case AndroidGeckoEvent::IME_SET_TEXT:
+        break;
+    case AndroidGeckoEvent::IME_UPDATE_COMPOSITION:
         {
-            NS_ASSERTION(mIMEComposing,
-                         "IME_SET_TEXT when we are not composing?!");
+            /*
+                Update the composition from ae->Start() to ae->End() using
+                  information from added ranges. This is only used for
+                  visual indication and does not affect the text content.
+                  Only the offsets are specified and not the text content
+                  to eliminate the possibility of this event altering the
+                  text content unintentionally.
 
-            OnIMEAddRange(ae);
+                Selection and text updates are masked so the result of
+                  temporary events are not passed on to Java
+            */
+            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+            AutoIMEMask textMask(mIMEMaskTextUpdate);
+            RemoveIMEComposition();
 
             nsTextEvent event(true, NS_TEXT_TEXT, this);
             InitEvent(event, nullptr);
 
-            event.theText.Assign(ae->Characters());
             event.rangeArray = mIMERanges.Elements();
             event.rangeCount = mIMERanges.Length();
 
+            {
+                nsSelectionEvent event(true, NS_SELECTION_SET, this);
+                InitEvent(event, nullptr);
+                event.mOffset = uint32_t(ae->Start());
+                event.mLength = uint32_t(ae->End() - ae->Start());
+                event.mExpandToClusterBoundary = false;
+                DispatchEvent(&event);
+            }
+            {
+                nsQueryContentEvent queryEvent(true,
+                        NS_QUERY_SELECTED_TEXT, this);
+                InitEvent(queryEvent, nullptr);
+                DispatchEvent(&queryEvent);
+                MOZ_ASSERT(queryEvent.mSucceeded && !queryEvent.mWasAsync);
+                event.theText = queryEvent.mReply.mString;
+            }
+            {
+                nsCompositionEvent event(true, NS_COMPOSITION_START, this);
+                InitEvent(event, nullptr);
+                DispatchEvent(&event);
+            }
+
             if (mIMEComposing &&
-                event.theText != mIMELastDispatchedComposingText) {
+                event.theText != mIMEComposingText) {
                 nsCompositionEvent compositionUpdate(true,
                                                      NS_COMPOSITION_UPDATE,
                                                      this);
                 InitEvent(compositionUpdate, nullptr);
                 compositionUpdate.data = event.theText;
-                mIMELastDispatchedComposingText = event.theText;
                 DispatchEvent(&compositionUpdate);
                 if (Destroyed())
                     return;
             }
 
 #ifdef DEBUG_ANDROID_IME
             const NS_ConvertUTF16toUTF8 theText8(event.theText);
             const char* text = theText8.get();
             ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
                     text, event.theText.Length(), mIMERanges.Length());
 #endif // DEBUG_ANDROID_IME
 
             DispatchEvent(&event);
             mIMERanges.Clear();
         }
-        return;
-    case AndroidGeckoEvent::IME_GET_TEXT:
-        {
-            ALOGIME("IME: IME_GET_TEXT: o=%u, l=%u", ae->Offset(), ae->Count());
-
-            nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, this);
-            InitEvent(event, nullptr);
-
-            event.InitForQueryTextContent(ae->Offset(), ae->Count());
-            
-            DispatchEvent(&event);
-
-            if (!event.mSucceeded) {
-                ALOGIME("IME:     -> failed");
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    nullptr, 0, 0, 0);
-                return;
-            } else if (!event.mWasAsync) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    event.mReply.mString.get(), 
-                    event.mReply.mString.Length(), 0, 0);
-            }
-        }
-        return;
-    case AndroidGeckoEvent::IME_DELETE_TEXT:
-        {
-            ALOGIME("IME: IME_DELETE_TEXT");
-            NS_ASSERTION(mIMEComposing,
-                         "IME_DELETE_TEXT when we are not composing?!");
-
-            nsKeyEvent event(true, NS_KEY_PRESS, this);
-            ANPEvent pluginEvent;
-            InitKeyEvent(event, *ae, &pluginEvent);
-            event.keyCode = NS_VK_BACK;
-            DispatchEvent(&event);
-        }
-        return;
-    case AndroidGeckoEvent::IME_SET_SELECTION:
+        break;
+    case AndroidGeckoEvent::IME_REMOVE_COMPOSITION:
         {
-            ALOGIME("IME: IME_SET_SELECTION: o=%u, l=%d", ae->Offset(), ae->Count());
-
-            nsSelectionEvent selEvent(true, NS_SELECTION_SET, this);
-            InitEvent(selEvent, nullptr);
-
-            selEvent.mOffset = uint32_t(ae->Count() >= 0 ?
-                                        ae->Offset() :
-                                        ae->Offset() + ae->Count());
-            selEvent.mLength = uint32_t(NS_ABS(ae->Count()));
-            selEvent.mReversed = ae->Count() >= 0 ? false : true;
-            selEvent.mExpandToClusterBoundary = false;
-
-            DispatchEvent(&selEvent);
+            RemoveIMEComposition();
+            mIMERanges.Clear();
         }
-        return;
-    case AndroidGeckoEvent::IME_GET_SELECTION:
-        {
-            ALOGIME("IME: IME_GET_SELECTION");
-
-            nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
-            InitEvent(event, nullptr);
-            DispatchEvent(&event);
-
-            if (!event.mSucceeded) {
-                ALOGIME("IME:     -> failed");
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    nullptr, 0, 0, 0);
-                return;
-            } else if (!event.mWasAsync) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(
-                    event.mReply.mString.get(),
-                    event.mReply.mString.Length(), 
-                    event.GetSelectionStart(),
-                    event.GetSelectionEnd() - event.GetSelectionStart());
-            }
-            //ALOGIME("IME:     -> o=%u, l=%u", event.mReply.mOffset, event.mReply.mString.Length());
-        }
-        return;
+        break;
     }
 }
 
 nsWindow *
 nsWindow::FindWindowForPoint(const nsIntPoint& pt)
 {
     if (!mBounds.Contains(pt))
         return nullptr;
@@ -2022,38 +2080,18 @@ nsWindow::UserActivity()
     mIdleService->ResetIdleTimeOut(0);
   }
 }
 
 NS_IMETHODIMP
 nsWindow::ResetInputState()
 {
     //ALOGIME("IME: ResetInputState: s=%d", aState);
-
-    // Cancel composition on Gecko side
-    if (mIMEComposing) {
-        nsRefPtr<nsWindow> kungFuDeathGrip(this);
-
-        nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
-        InitEvent(textEvent, nullptr);
-        textEvent.theText = mIMEComposingText;
-        DispatchEvent(&textEvent);
-        mIMEComposingText.Truncate(0);
-
-        nsCompositionEvent event(true, NS_COMPOSITION_END, this);
-        InitEvent(event, nullptr);
-        DispatchEvent(&event);
-    }
-
+    RemoveIMEComposition();
     AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_RESETINPUTSTATE, 0);
-
-    // Send IME text/selection change notifications
-    OnIMETextChange(0, 0, 0);
-    OnIMESelectionChange();
-
     return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 nsWindow::SetInputContext(const InputContext& aContext,
                           const InputContextAction& aAction)
 {
     ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
@@ -2103,17 +2141,16 @@ nsWindow::CancelIMEComposition()
 
     // Cancel composition on Gecko side
     if (mIMEComposing) {
         nsRefPtr<nsWindow> kungFuDeathGrip(this);
 
         nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
         InitEvent(textEvent, nullptr);
         DispatchEvent(&textEvent);
-        mIMEComposingText.Truncate(0);
 
         nsCompositionEvent compEvent(true, NS_COMPOSITION_END, this);
         InitEvent(compEvent, nullptr);
         DispatchEvent(&compEvent);
     }
 
     AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_CANCELCOMPOSITION, 0);
     return NS_OK;
@@ -2123,75 +2160,68 @@ NS_IMETHODIMP
 nsWindow::OnIMEFocusChange(bool aFocus)
 {
     ALOGIME("IME: OnIMEFocusChange: f=%d", aFocus);
 
     AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_FOCUSCHANGE, 
                              int(aFocus));
 
     if (aFocus) {
-        OnIMETextChange(0, 0, 0);
+        OnIMETextChange(0, INT32_MAX, INT32_MAX);
         OnIMESelectionChange();
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::OnIMETextChange(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd)
 {
+    if (mIMEMaskTextUpdate)
+        return NS_OK;
+
     ALOGIME("IME: OnIMETextChange: s=%d, oe=%d, ne=%d",
             aStart, aOldEnd, aNewEnd);
 
-    if (!mInputContext.mIMEState.mEnabled) {
-        AndroidBridge::NotifyIMEChange(nullptr, 0, 0, 0, 0);
-        return NS_OK;
-    }
-
-    // A quirk in Android makes it necessary to pass the whole text.
-    // The more efficient way would have been passing the substring from index
-    // aStart to index aNewEnd
-
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, this);
     InitEvent(event, nullptr);
-    event.InitForQueryTextContent(0, UINT32_MAX);
+    event.InitForQueryTextContent(aStart, aNewEnd - aStart);
 
     DispatchEvent(&event);
     if (!event.mSucceeded)
         return NS_OK;
 
     AndroidBridge::NotifyIMEChange(event.mReply.mString.get(),
                                    event.mReply.mString.Length(),
                                    aStart, aOldEnd, aNewEnd);
 
+    /* Make sure Java's selection is up-to-date */
+    OnIMESelectionChange();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::OnIMESelectionChange(void)
 {
-    ALOGIME("IME: OnIMESelectionChange");
+    if (mIMEMaskSelectionUpdate)
+        return NS_OK;
 
-    if (!mInputContext.mIMEState.mEnabled) {
-        AndroidBridge::NotifyIMEChange(nullptr, 0, 0, 0, -1);
-        return NS_OK;
-    }
+    ALOGIME("IME: OnIMESelectionChange");
 
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
     InitEvent(event, nullptr);
 
     DispatchEvent(&event);
     if (!event.mSucceeded)
         return NS_OK;
 
-    AndroidBridge::NotifyIMEChange(nullptr, 0, int(event.mReply.mOffset),
-                                   int(event.mReply.mOffset + 
-                                       event.mReply.mString.Length()), -1);
+    AndroidBridge::NotifyIMEChange(nullptr, 0, int(event.GetSelectionStart()),
+                                   int(event.GetSelectionEnd()), -1);
     return NS_OK;
 }
 
 nsIMEUpdatePreference
 nsWindow::GetIMEUpdatePreference()
 {
     return nsIMEUpdatePreference(true, true);
 }
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -161,35 +161,35 @@ public:
 #endif
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool DrawTo(gfxASurface *targetSurface);
     bool DrawTo(gfxASurface *targetSurface, const nsIntRect &aRect);
     bool IsTopLevel();
-    void OnIMEAddRange(mozilla::AndroidGeckoEvent *ae);
+    void RemoveIMEComposition();
 
     // Call this function when the users activity is the direct cause of an
     // event (like a keypress or mouse click).
     void UserActivity();
 
     bool mIsVisible;
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
     nsWindow* mFocus;
 
     double mStartDist;
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
     bool mIMEComposing;
+    bool mIMEMaskSelectionUpdate, mIMEMaskTextUpdate;
     nsString mIMEComposingText;
-    nsString mIMELastDispatchedComposingText;
     nsAutoTArray<nsTextRange, 4> mIMERanges;
 
     InputContext mInputContext;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
--- a/widget/cocoa/GfxInfo.h
+++ b/widget/cocoa/GfxInfo.h
@@ -48,16 +48,18 @@ public:
 
   virtual nsresult Init();
 
 #ifdef DEBUG
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIGFXINFODEBUG
 #endif
 
+  virtual uint32_t OperatingSystemVersion() const { return mOSXVersion; }
+
 protected:
 
   virtual nsresult GetFeatureStatusImpl(int32_t aFeature, 
                                         int32_t *aStatus, 
                                         nsAString & aSuggestedDriverVersion, 
                                         const nsTArray<GfxDriverInfo>& aDriverInfo,
                                         OperatingSystem* aOS = nullptr);
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -178,20 +178,16 @@ typedef NSInteger NSEventGestureAxis;
 // automatically firing momentum scroll events.
 @interface NSEvent (ScrollPhase)
 // Leopard and SnowLeopard
 - (long long)_scrollPhase;
 // Lion and above
 - (NSEventPhase)momentumPhase;
 @end
 
-@protocol EventRedirection
-  - (NSView*)targetView;
-@end
-
 @interface ChildView : NSView<
 #ifdef ACCESSIBILITY
                               mozAccessible,
 #endif
                               mozView, NSTextInput>
 {
 @private
   // the nsChildView that created the view. It retains this NSView, so
@@ -266,29 +262,25 @@ typedef NSInteger NSEventGestureAxis;
 
   // Whether this uses off-main-thread compositing.
   BOOL mUsingOMTCompositor;
 }
 
 // class initialization
 + (void)initialize;
 
-+ (void)registerViewForDraggedTypes:(NSView*)aView;
-
 // these are sent to the first responder when the window key status changes
 - (void)viewsWindowDidBecomeKey;
 - (void)viewsWindowDidResignKey;
 
 // Stop NSView hierarchy being changed during [ChildView drawRect:]
 - (void)delayedTearDown;
 
 - (void)sendFocusEvent:(uint32_t)eventType;
 
-- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent;
-
 - (void)handleMouseMoved:(NSEvent*)aEvent;
 
 - (void)drawRect:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext;
 
 - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
                             enter:(BOOL)aEnter
                              type:(nsMouseEvent::exitType)aType;
 
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -1907,30 +1907,16 @@ NSEvent* gLastDragMouseDownEvent = nil;
 
     [sendTypes release];
     [returnTypes release];
 
     initialized = YES;
   }
 }
 
-+ (void)registerViewForDraggedTypes:(NSView*)aView
-{
-  [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
-                                                           NSStringPboardType,
-                                                           NSHTMLPboardType,
-                                                           NSURLPboardType,
-                                                           NSFilesPromisePboardType,
-                                                           kWildcardPboardType,
-                                                           kCorePboardType_url,
-                                                           kCorePboardType_urld,
-                                                           kCorePboardType_urln,
-                                                           nil]];
-}
-
 // initWithFrame:geckoChild:
 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ((self = [super initWithFrame:inFrame])) {
     mGeckoChild = inChild;
     mIsPluginView = NO;
@@ -1968,18 +1954,27 @@ NSEvent* gLastDragMouseDownEvent = nil;
     [self setFocusRingType:NSFocusRingTypeNone];
 
 #ifdef __LP64__
     mSwipeAnimationCancelled = nil;
 #endif
   }
   
   // register for things we'll take from other applications
-  [ChildView registerViewForDraggedTypes:self];
-
+  PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView initWithFrame: registering drag types\n"));
+  [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
+                                                          NSStringPboardType,
+                                                          NSHTMLPboardType,
+                                                          NSURLPboardType,
+                                                          NSFilesPromisePboardType,
+                                                          kWildcardPboardType,
+                                                          kCorePboardType_url,
+                                                          kCorePboardType_urld,
+                                                          kCorePboardType_urln,
+                                                          nil]];
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(windowBecameMain:)
                                                name:NSWindowDidBecomeMainNotification
                                              object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(windowResignedMain:)
                                                name:NSWindowDidResignMainNotification
                                              object:nil];
@@ -2339,17 +2334,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
   }
   [super scrollRect:aRect by:offset];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (BOOL)mouseDownCanMoveWindow
 {
-  return [[self window] isMovableByWindowBackground];
+  return NO;
 }
 
 - (void)lockFocus
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [super lockFocus];
 
@@ -3285,35 +3280,16 @@ NSEvent* gLastDragMouseDownEvent = nil;
   }
 
   event.exit = aType;
 
   nsEventStatus status; // ignored
   mGeckoChild->DispatchEvent(&event, status);
 }
 
-- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent
-{
-  if (!theEvent || !mGeckoChild) {
-    return;
-  }
-
-  nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget();
-  if (!windowWidget) {
-    return;
-  }
-
-  // We assume later on that sending a hit test event won't cause widget destruction.
-  nsMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild, nsMouseEvent::eReal);
-  [self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent];
-  bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent);
-
-  [windowWidget->GetCocoaWindow() setMovableByWindowBackground:result];
-}
-
 - (void)handleMouseMoved:(NSEvent*)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (!mGeckoChild)
     return;
 
   nsMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, nsMouseEvent::eReal);
@@ -4828,21 +4804,16 @@ ChildView*
 ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
 {
   NSWindow* window = sWindowUnderMouse;
   if (!window)
     return nil;
 
   NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
   NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
-
-  while([view conformsToProtocol:@protocol(EventRedirection)]) {
-    view = [(id<EventRedirection>)view targetView];
-  }
-
   if (![view isKindOfClass:[ChildView class]])
     return nil;
 
   ChildView* childView = (ChildView*)view;
   // If childView is being destroyed return nil.
   if (![childView widget])
     return nil;
   return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
@@ -4931,17 +4902,17 @@ ChildViewMouseTracker::WindowAcceptsEven
 // triggers bmo bugs 431902 and 476393.  So here we make sure that a
 // ToolbarWindow's contentView always returns NO from the
 // mouseDownCanMoveWindow method.
 - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow
 {
   NSWindow *ourWindow = [self window];
   NSView *contentView = [ourWindow contentView];
   if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
-    return [ourWindow isMovableByWindowBackground];
+    return NO;
   return [self nsChildView_NSView_mouseDownCanMoveWindow];
 }
 
 @end
 
 #ifdef __LP64__
 // When using blocks, at least on OS X 10.7, the OS sometimes calls
 // +[NSEvent removeMonitor:] more than once on a single event monitor, which
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -13,17 +13,16 @@
 #include "nsBaseWidget.h"
 #include "nsPIWidgetCocoa.h"
 #include "nsAutoPtr.h"
 #include "nsCocoaUtils.h"
 
 class nsCocoaWindow;
 class nsChildView;
 class nsMenuBarX;
-@class ChildView;
 
 // Value copied from BITMAP_MAX_AREA, used in nsNativeThemeCocoa.mm
 #define CUIDRAW_MAX_AREA 500000
 
 // If we are using an SDK older than 10.7, define bits we need that are missing
 // from it.
 #if !defined(MAC_OS_X_VERSION_10_7) || \
     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
@@ -172,28 +171,26 @@ typedef struct _nsCocoaWindowList {
 @end
 
 // NSWindow subclass for handling windows with toolbars.
 @interface ToolbarWindow : BaseWindow
 {
   TitlebarAndBackgroundColor *mColor;
   float mUnifiedToolbarHeight;
   NSColor *mBackgroundColor;
-  NSView *mTitlebarView; // strong
 }
 // Pass nil here to get the default appearance.
 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
 - (void)setUnifiedToolbarHeight:(float)aHeight;
 - (float)unifiedToolbarHeight;
 - (float)titlebarHeight;
 - (NSRect)titlebarRect;
 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
-- (ChildView*)mainChildView;
 @end
 
 class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa
 {
 private:
   
   typedef nsBaseWidget Inherited;
 
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -442,20 +442,16 @@ nsresult nsCocoaWindow::CreateNativeWind
     [mWindow setHasShadow:YES];
   }
 
   [mWindow setBackgroundColor:[NSColor clearColor]];
   [mWindow setOpaque:NO];
   [mWindow setContentMinSize:NSMakeSize(60, 60)];
   [mWindow disableCursorRects];
 
-  // Make sure the window starts out not draggable by the background.
-  // We will turn it on as necessary.
-  [mWindow setMovableByWindowBackground:NO];
-
   [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
   mWindowMadeHere = true;
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
@@ -2582,104 +2578,17 @@ static const NSString* kStateShowsToolba
     retval = [NSArray arrayWithArray:holder];
   }
 
   return retval;
 }
 
 @end
 
-@interface TitlebarMouseHandlingView : NSView<EventRedirection>
-{
-  ToolbarWindow* mWindow; // weak
-  BOOL mProcessingRightMouseDown;
-}
-
-- (id)initWithWindow:(ToolbarWindow*)aWindow;
-@end
-
-@implementation TitlebarMouseHandlingView
-
-- (id)initWithWindow:(ToolbarWindow*)aWindow
-{
-  if ((self = [super initWithFrame:[aWindow titlebarRect]])) {
-    mWindow = aWindow;
-    [self setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
-    [ChildView registerViewForDraggedTypes:self];
-    mProcessingRightMouseDown = NO;
-  }
-  return self;
-}
-
-- (NSView*)targetView
-{
-  return [mWindow mainChildView];
-}
-
-- (BOOL)mouseDownCanMoveWindow
-{
-  return [mWindow isMovableByWindowBackground];
-}
-
-// We redirect many types of events to the window's mainChildView simply by
-// passing the event object to the respective handler method. We don't need any
-// coordinate transformations because event coordinates are relative to the
-// window.
-// We only need to handle event types whose target NSView is determined by the
-// event's position. We don't need to handle key events and NSMouseMoved events
-// because those are only sent to the window's first responder. This view
-// doesn't override acceptsFirstResponder, so it will never receive those kinds
-// of events.
-
-- (void)mouseMoved:(NSEvent*)aEvent            { [[self targetView] mouseMoved:aEvent]; }
-- (void)mouseDown:(NSEvent*)aEvent             { [[self targetView] mouseDown:aEvent]; }
-- (void)mouseUp:(NSEvent*)aEvent               { [[self targetView] mouseUp:aEvent]; }
-- (void)mouseDragged:(NSEvent*)aEvent          { [[self targetView] mouseDragged:aEvent]; }
-- (void)rightMouseDown:(NSEvent*)aEvent
-{
-  // To avoid recursion...
-  if (mProcessingRightMouseDown)
-    return;
-  mProcessingRightMouseDown = YES;
-  [[self targetView] rightMouseDown:aEvent];
-  mProcessingRightMouseDown = NO;
-}
-- (void)rightMouseUp:(NSEvent*)aEvent          { [[self targetView] rightMouseUp:aEvent]; }
-- (void)rightMouseDragged:(NSEvent*)aEvent     { [[self targetView] rightMouseDragged:aEvent]; }
-- (void)otherMouseDown:(NSEvent*)aEvent        { [[self targetView] otherMouseDown:aEvent]; }
-- (void)otherMouseUp:(NSEvent*)aEvent          { [[self targetView] otherMouseUp:aEvent]; }
-- (void)otherMouseDragged:(NSEvent*)aEvent     { [[self targetView] otherMouseDragged:aEvent]; }
-- (void)scrollWheel:(NSEvent*)aEvent           { [[self targetView] scrollWheel:aEvent]; }
-- (void)swipeWithEvent:(NSEvent*)aEvent        { [[self targetView] swipeWithEvent:aEvent]; }
-- (void)beginGestureWithEvent:(NSEvent*)aEvent { [[self targetView] beginGestureWithEvent:aEvent]; }
-- (void)magnifyWithEvent:(NSEvent*)aEvent      { [[self targetView] magnifyWithEvent:aEvent]; }
-- (void)rotateWithEvent:(NSEvent*)aEvent       { [[self targetView] rotateWithEvent:aEvent]; }
-- (void)endGestureWithEvent:(NSEvent*)aEvent   { [[self targetView] endGestureWithEvent:aEvent]; }
-- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
-  { return [[self targetView] draggingEntered:sender]; }
-- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
-  { return [[self targetView] draggingUpdated:sender]; }
-- (void)draggingExited:(id <NSDraggingInfo>)sender
-  { [[self targetView] draggingExited:sender]; }
-- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
-  { return [[self targetView] performDragOperation:sender]; }
-- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
-  { [[self targetView] draggedImage:anImage endedAt:aPoint operation:operation]; }
-- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
-  { return [[self targetView] draggingSourceOperationMaskForLocal:isLocal]; }
-- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
-  { return [[self targetView] namesOfPromisedFilesDroppedAtDestination:dropDestination]; }
-- (NSMenu*)menuForEvent:(NSEvent*)aEvent
-  { return [[self targetView] menuForEvent:aEvent]; }
-
-@end
-
-// This class allows us to exercise control over the window's title bar. This
-// allows for a "unified toolbar" look, and for extending the content area into
-// the title bar. It works like this:
+// This class allows us to have a "unified toolbar" style window. It works like this:
 // 1) We set the window's style to textured.
 // 2) Because of this, the background color applies to the entire window, including
 //     the titlebar area. For normal textured windows, the default pattern is a 
 //    "brushed metal" image on Tiger and a unified gradient on Leopard.
 // 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
 //    When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
 //    it paints the the titlebar and background colors in the correct areas of the context it's given,
 //    which will fill the entire window (CG will tile it horizontally for us).
@@ -2703,21 +2612,16 @@ static const NSString* kStateShowsToolba
 //
 // Whenever the unified gradient is drawn in the titlebar or the toolbar, both
 // titlebar height and toolbar height must be known in order to construct the
 // correct gradient (which is a linear gradient with the length
 // titlebarHeight + toolbarHeight - 1). But you can only get from the toolbar frame
 // to the containing window - the other direction doesn't work. That's why the
 // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
 // query the window for its titlebar height when drawing the toolbar.
-@interface ToolbarWindow(Private)
-- (void)installTitlebarMouseHandlingView;
-- (void)uninstallTitlebarMouseHandlingView;
-@end;
-
 @implementation ToolbarWindow
 
 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   aStyle = aStyle | NSTexturedBackgroundWindowMask;
   if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
@@ -2742,17 +2646,16 @@ static const NSString* kStateShowsToolba
 }
 
 - (void)dealloc
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [mColor release];
   [mBackgroundColor release];
-  [mTitlebarView release];
   [super dealloc];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
 {
   [super setTitlebarColor:aColor forActiveWindow:aActive];
@@ -2822,101 +2725,39 @@ static const NSString* kStateShowsToolba
   [self setContentBorderThickness:mUnifiedToolbarHeight forEdge:NSMaxYEdge];
 
   // Redraw the title bar. If we're inside painting, we'll do it right now,
   // otherwise we'll just invalidate it.
   BOOL needSyncRedraw = ([NSView focusView] != nil);
   [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
 }
 
-// Extending the content area into the title bar works by redirection of both
-// drawing and mouse events.
-// The window's NSView hierarchy looks like this:
-//  - border view ([[window contentView] superview])
-//     - transparent title bar event redirection view
-//     - window controls (traffic light buttons)
-//     - content view ([window contentView], default NSView provided by the window)
-//        - our main Gecko ChildView ([window mainChildView]), which has an
-//          OpenGL context attached to it when accelerated
-//           - possibly more ChildViews for plugins
-//
-// When the window is in title bar extension mode, the mainChildView covers the
-// whole window but is only visible in the content area of the window, because
-// it's a subview of the window's contentView and thus clipped to its dimensions.
-// This clipping is a good thing because it avoids a few problems. For example,
-// if the mainChildView weren't clipped and thus visible in the titlebar, we'd
-// have have to do the rounded corner masking and the drawing of the highlight
-// line ourselves.
-// This would be especially hard in combination with OpenGL acceleration since
-// rounded corners would require making the OpenGL context transparent, which
-// would bring another set of challenges with it. Having the window controls
-// draw on top of an OpenGL context could be hard, too.
-//
-// So title bar drawing happens in the border view. The border view's drawRect
-// method is not under our control, but we can get it to call into our code
-// using some tricks, see the TitlebarAndBackgroundColor class below.
-// Specifically, we have it call the TitlebarDrawCallback function, which
-// draws the contents of mainChildView into the provided CGContext.
-// (Even if the ChildView uses OpenGL for rendering, drawing in the title bar
-// will happen non-accelerated in that CGContext.)
-//
-// Mouse event redirection happens via a TitlebarMouseHandlingView which we
-// install below.
 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
 {
   BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
   [super setDrawsContentsIntoWindowFrame:aState];
   if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
-    // Here we extend / shrink our mainChildView. We do that by firing a resize
-    // event which will cause the ChildView to be resized to the rect returned
-    // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
-    // value on what we return from drawsContentsIntoWindowFrame.
     WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
     nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
     if (geckoWindow) {
       // Re-layout our contents.
       geckoWindow->ReportSizeEvent();
     }
 
     // Resizing the content area causes a reflow which would send a synthesized
     // mousemove event to the old mouse position relative to the top left
     // corner of the content area. But the mouse has shifted relative to the
     // content area, so that event would have wrong position information. So
     // we'll send a mouse move event with the correct new position.
     ChildViewMouseTracker::ResendLastMouseMoveEvent();
 
-    if (aState) {
-      [self installTitlebarMouseHandlingView];
-    } else {
-      [self uninstallTitlebarMouseHandlingView];
-    }
+    [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
   }
 }
 
-- (void)installTitlebarMouseHandlingView
-{
-  mTitlebarView = [[TitlebarMouseHandlingView alloc] initWithWindow:self];
-  [[[self contentView] superview] addSubview:mTitlebarView positioned:NSWindowBelow relativeTo:nil];
-}
-
-- (void)uninstallTitlebarMouseHandlingView
-{
-  [mTitlebarView removeFromSuperview];
-  [mTitlebarView release];
-  mTitlebarView = nil;
-}
-
-- (ChildView*)mainChildView
-{
-  NSView* view = [[[self contentView] subviews] lastObject];
-  if (view && [view isKindOfClass:[ChildView class]])
-    return (ChildView*)view;
-  return nil;
-}
-
 // Returning YES here makes the setShowsToolbarButton method work even though
 // the window doesn't contain an NSToolbar.
 - (BOOL)_hasToolbar
 {
   return YES;
 }
 
 // Dispatch a toolbar pill button clicked message to Gecko.
@@ -2975,19 +2816,16 @@ static const NSString* kStateShowsToolba
     {
       // Drop all mouse events if a modal window has appeared above us.
       // This helps make us behave as if the OS were running a "real" modal
       // event loop.
       id delegate = [self delegate];
       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
         nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
         if (widget) {
-          if (type == NSMouseMoved) {
-            [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent];
-          }
           if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
             return;
           if (widget->HasModalDescendents())
             return;
         }
       }
       break;
     }
@@ -3043,29 +2881,29 @@ DrawNativeTitlebar(CGContextRef aContext
 // Pattern draw callback for standard titlebar gradients and solid titlebar colors
 static void
 TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
 {
   ToolbarWindow *window = (ToolbarWindow*)aInfo;
   NSRect titlebarRect = [window titlebarRect];
 
   if ([window drawsContentsIntoWindowFrame]) {
-    ChildView* view = [window mainChildView];
-    if (!view)
+    NSView* view = [[[window contentView] subviews] lastObject];
+    if (!view || ![view isKindOfClass:[ChildView class]])
       return;
 
     // Gecko drawing assumes flippedness, but the current context isn't flipped
     // (because we're painting into the window's border view, which is not a
     // ChildView, so it isn't flipped).
     // So we need to set a flip transform.
     CGContextScaleCTM(aContext, 1.0f, -1.0f);
     CGContextTranslateCTM(aContext, 0.0f, -[window frame].size.height);
 
     NSRect flippedTitlebarRect = { NSZeroPoint, titlebarRect.size };
-    [view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
+    [(ChildView*)view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
   } else {
     BOOL isMain = [window isMainWindow];
     NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
     if (!titlebarColor) {
       // If the titlebar color is nil, draw the default titlebar shading.
       DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
                          [window unifiedToolbarHeight], isMain);
     } else {
--- a/widget/nsIGfxInfo.idl
+++ b/widget/nsIGfxInfo.idl
@@ -3,17 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 /* NOTE: this interface is completely undesigned, not stable and likely to change */
 
-[scriptable, uuid(a67c77af-2952-4028-93ab-e7bc3b43cf81)]
+[scriptable, uuid(8a9797ae-22d4-431d-a628-18fd5900c53c)]
 interface nsIGfxInfo : nsISupports
 {
   /*
    * These are win32-specific
    */
   readonly attribute boolean D2DEnabled;
   readonly attribute boolean DWriteEnabled;
   readonly attribute DOMString DWriteVersion;
@@ -72,16 +72,18 @@ interface nsIGfxInfo : nsISupports
   /* Whether OpenGL is supported for layers */
   const long FEATURE_OPENGL_LAYERS = 5;
   /* Whether WebGL is supported via OpenGL. */
   const long FEATURE_WEBGL_OPENGL = 6;
   /* Whether WebGL is supported via ANGLE (D3D9 -- does not check for the presence of ANGLE libs). */
   const long FEATURE_WEBGL_ANGLE = 7;
   /* Whether WebGL antialiasing is supported. */
   const long FEATURE_WEBGL_MSAA = 8;
+  /* Whether Stagefright is supported */
+  const long FEATURE_STAGEFRIGHT = 9;
 
   /*
    * A set of return values from GetFeatureStatus
    */
 
   /* We don't explicitly block or discourage the feature. Which means we'll try getting it from the
    * hardware, and see what happens. */
   const long FEATURE_NO_INFO = 1;
--- a/widget/windows/GfxInfo.h
+++ b/widget/windows/GfxInfo.h
@@ -40,16 +40,18 @@ public:
   NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
   NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
   using GfxInfoBase::GetWebGLParameter;
 
   virtual nsresult Init();
 
+  virtual uint32_t OperatingSystemVersion() const { return mWindowsVersion; }
+
 #ifdef DEBUG
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIGFXINFODEBUG
 #endif
 
 protected:
 
   virtual nsresult GetFeatureStatusImpl(int32_t aFeature, 
--- a/widget/xpwidgets/GfxDriverInfo.cpp
+++ b/widget/xpwidgets/GfxDriverInfo.cpp
@@ -12,16 +12,17 @@ int32_t GfxDriverInfo::allFeatures = 0;
 uint64_t GfxDriverInfo::allDriverVersions = ~(uint64_t(0));
 GfxDeviceFamily* const GfxDriverInfo::allDevices = nullptr;
 
 GfxDeviceFamily* GfxDriverInfo::mDeviceFamilies[DeviceFamilyMax];
 nsAString* GfxDriverInfo::mDeviceVendors[DeviceVendorMax];
 
 GfxDriverInfo::GfxDriverInfo()
   : mOperatingSystem(DRIVER_OS_UNKNOWN),
+    mOperatingSystemVersion(0),
     mAdapterVendor(GfxDriverInfo::GetDeviceVendor(VendorAll)),
     mDevices(allDevices),
     mDeleteDevices(false),
     mFeature(allFeatures),
     mFeatureStatus(nsIGfxInfo::FEATURE_NO_INFO),
     mComparisonOp(DRIVER_COMPARISON_IGNORED),
     mDriverVersion(0),
     mDriverVersionMax(0),
@@ -31,29 +32,31 @@ GfxDriverInfo::GfxDriverInfo()
 GfxDriverInfo::GfxDriverInfo(OperatingSystem os, nsAString& vendor,
                              GfxDeviceFamily* devices,
                              int32_t feature, int32_t featureStatus,
                              VersionComparisonOp op,
                              uint64_t driverVersion,
                              const char *suggestedVersion /* = nullptr */,
                              bool ownDevices /* = false */)
   : mOperatingSystem(os),
+    mOperatingSystemVersion(0),
     mAdapterVendor(vendor),
     mDevices(devices),
     mDeleteDevices(ownDevices),
     mFeature(feature),
     mFeatureStatus(featureStatus),
     mComparisonOp(op),
     mDriverVersion(driverVersion),
     mDriverVersionMax(0),
     mSuggestedVersion(suggestedVersion)
 {}
 
 GfxDriverInfo::GfxDriverInfo(const GfxDriverInfo& aOrig)
   : mOperatingSystem(aOrig.mOperatingSystem),
+    mOperatingSystemVersion(aOrig.mOperatingSystemVersion),
     mAdapterVendor(aOrig.mAdapterVendor),
     mFeature(aOrig.mFeature),
     mFeatureStatus(aOrig.mFeatureStatus),
     mComparisonOp(aOrig.mComparisonOp),
     mDriverVersion(aOrig.mDriverVersion),
     mDriverVersionMax(aOrig.mDriverVersionMax),
     mSuggestedVersion(aOrig.mSuggestedVersion)
 {
--- a/widget/xpwidgets/GfxDriverInfo.h
+++ b/widget/xpwidgets/GfxDriverInfo.h
@@ -81,16 +81,17 @@ struct GfxDriverInfo
                 uint64_t driverVersion, const char *suggestedVersion = nullptr,
                 bool ownDevices = false);
 
   GfxDriverInfo();
   GfxDriverInfo(const GfxDriverInfo&);
   ~GfxDriverInfo();
 
   OperatingSystem mOperatingSystem;
+  uint32_t mOperatingSystemVersion;
 
   nsString mAdapterVendor;
 
   static GfxDeviceFamily* const allDevices;
   GfxDeviceFamily* mDevices;
 
   // Whether the mDevices array should be deleted when this structure is
   // deallocated. False by default.
@@ -112,16 +113,18 @@ struct GfxDriverInfo
 
   const char *mSuggestedVersion;
 
   static const GfxDeviceFamily* GetDeviceFamily(DeviceFamily id);
   static GfxDeviceFamily* mDeviceFamilies[DeviceFamilyMax];
 
   static const nsAString& GetDeviceVendor(DeviceVendor id);
   static nsAString* mDeviceVendors[DeviceVendorMax];
+
+  nsString mModel, mHardware, mProduct, mManufacturer;
 };
 
 #define GFX_DRIVER_VERSION(a,b,c,d) \
   ((uint64_t(a)<<48) | (uint64_t(b)<<32) | (uint64_t(c)<<16) | uint64_t(d))
 
 static uint64_t
 V(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
 {
--- a/widget/xpwidgets/GfxInfoBase.cpp
+++ b/widget/xpwidgets/GfxInfoBase.cpp
@@ -116,16 +116,19 @@ GetPrefNameForFeature(int32_t aFeature)
       name = BLACKLIST_PREF_BRANCH "webgl.opengl";
       break;
     case nsIGfxInfo::FEATURE_WEBGL_ANGLE:
       name = BLACKLIST_PREF_BRANCH "webgl.angle";
       break;
     case nsIGfxInfo::FEATURE_WEBGL_MSAA:
       name = BLACKLIST_PREF_BRANCH "webgl.msaa";
       break;
+    case nsIGfxInfo::FEATURE_STAGEFRIGHT:
+      name = BLACKLIST_PREF_BRANCH "stagefright";
+      break;
     default:
       break;
   };
 
   return name;
 }
 
 // Returns the value of the pref for the relevant feature in aValue.
@@ -265,17 +268,18 @@ BlacklistFeatureToGfxFeature(const nsASt
   else if (aFeature == NS_LITERAL_STRING("OPENGL_LAYERS"))
     return nsIGfxInfo::FEATURE_OPENGL_LAYERS;
   else if (aFeature == NS_LITERAL_STRING("WEBGL_OPENGL"))
     return nsIGfxInfo::FEATURE_WEBGL_OPENGL;
   else if (aFeature == NS_LITERAL_STRING("WEBGL_ANGLE"))
     return nsIGfxInfo::FEATURE_WEBGL_ANGLE;
   else if (aFeature == NS_LITERAL_STRING("WEBGL_MSAA"))
     return nsIGfxInfo::FEATURE_WEBGL_MSAA;
-
+  else if (aFeature == NS_LITERAL_STRING("STAGEFRIGHT"))
+    return nsIGfxInfo::FEATURE_STAGEFRIGHT;
   return 0;
 }
 
 static int32_t
 BlacklistFeatureStatusToGfxFeatureStatus(const nsAString& aStatus)
 {
   if (aStatus == NS_LITERAL_STRING("NO_INFO"))
     return nsIGfxInfo::FEATURE_NO_INFO;
@@ -374,16 +378,23 @@ BlacklistEntryToDriverInfo(nsIDOMNode* a
 
   // <os>WINNT 6.0</os>
   if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("os"),
                                   getter_AddRefs(dataNode))) {
     BlacklistNodeToTextValue(dataNode, dataValue);
     aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
   }
 
+  // <osversion>14</osversion> currently only used for Android
+  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("osversion"),
+                                  getter_AddRefs(dataNode))) {
+    BlacklistNodeToTextValue(dataNode, dataValue);
+    aDriverInfo.mOperatingSystemVersion = strtoul(NS_LossyConvertUTF16toASCII(dataValue).get(), NULL, 10);
+  }
+
   // <vendor>0x8086</vendor>
   if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("vendor"),
                                   getter_AddRefs(dataNode))) {
     BlacklistNodeToTextValue(dataNode, dataValue);
     aDriverInfo.mAdapterVendor = dataValue;
   }
 
   // <devices>
@@ -435,16 +446,41 @@ BlacklistEntryToDriverInfo(nsIDOMNode* a
 
   // <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
   if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("driverVersionComparator"),
                                   getter_AddRefs(dataNode))) {
     BlacklistNodeToTextValue(dataNode, dataValue);
     aDriverInfo.mComparisonOp = BlacklistComparatorToComparisonOp(dataValue);
   }
 
+  // <model>foo</model>
+  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("model"),
+                                  getter_AddRefs(dataNode))) {
+    BlacklistNodeToTextValue(dataNode, dataValue);
+    aDriverInfo.mModel = dataValue;
+  }
+  // <product>foo</product>
+  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("product"),
+                                  getter_AddRefs(dataNode))) {
+    BlacklistNodeToTextValue(dataNode, dataValue);
+    aDriverInfo.mProduct = dataValue;
+  }
+  // <manufacturer>foo</manufacturer>
+  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("manufacturer"),
+                                  getter_AddRefs(dataNode))) {
+    BlacklistNodeToTextValue(dataNode, dataValue);
+    aDriverInfo.mManufacturer = dataValue;
+  }
+  // <hardware>foo</hardware>
+  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("hardware"),
+                                  getter_AddRefs(dataNode))) {
+    BlacklistNodeToTextValue(dataNode, dataValue);
+    aDriverInfo.mHardware = dataValue;
+  }
+
   // We explicitly ignore unknown elements.
 
   return true;
 }
 
 static void
 BlacklistEntriesToDriverInfo(nsIDOMHTMLCollection* aBlacklistEntries,
                              nsTArray<GfxDriverInfo>& aDriverInfo)
@@ -550,16 +586,20 @@ GfxInfoBase::FindBlocklistedDeviceInList
   uint32_t i = 0;
   for (; i < info.Length(); i++) {
     if (info[i].mOperatingSystem != DRIVER_OS_ALL &&
         info[i].mOperatingSystem != os)
     {
       continue;
     }
 
+    if (info[i].mOperatingSystemVersion && info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
+        continue;
+    }
+
     if (!info[i].mAdapterVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll), nsCaseInsensitiveStringComparator()) &&
         !info[i].mAdapterVendor.Equals(adapterVendorID, nsCaseInsensitiveStringComparator())) {
       continue;
     }
 
     if (info[i].mDevices != GfxDriverInfo::allDevices && info[i].mDevices->Length()) {
         bool deviceMatches = false;
         for (uint32_t j = 0; j < info[i].mDevices->Length(); j++) {
@@ -571,16 +611,29 @@ GfxInfoBase::FindBlocklistedDeviceInList
 
         if (!deviceMatches) {
             continue;
         }
     }
 
     bool match = false;
 
+    if (!info[i].mHardware.IsEmpty() && !info[i].mHardware.Equals(Hardware())) {
+        continue;
+    }
+    if (!info[i].mModel.IsEmpty() && !info[i].mModel.Equals(Model())) {
+        continue;
+    }
+    if (!info[i].mProduct.IsEmpty() && !info[i].mProduct.Equals(Product())) {
+        continue;
+    }
+    if (!info[i].mManufacturer.IsEmpty() && !info[i].mManufacturer.Equals(Manufacturer())) {
+        continue;
+    }
+
 #if defined(XP_WIN) || defined(ANDROID)
     switch (info[i].mComparisonOp) {
     case DRIVER_LESS_THAN:
       match = driverVersion < info[i].mDriverVersion;
       break;
     case DRIVER_LESS_THAN_OR_EQUAL:
       match = driverVersion <= info[i].mDriverVersion;
       break;
@@ -736,16 +789,17 @@ GfxInfoBase::EvaluateDownloadedBlacklist
     nsIGfxInfo::FEATURE_DIRECT2D,
     nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
     nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS,
     nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS,
     nsIGfxInfo::FEATURE_OPENGL_LAYERS,
     nsIGfxInfo::FEATURE_WEBGL_OPENGL,
     nsIGfxInfo::FEATURE_WEBGL_ANGLE,
     nsIGfxInfo::FEATURE_WEBGL_MSAA,
+    nsIGfxInfo::FEATURE_STAGEFRIGHT,
     0
   };
 
   // For every feature we know about, we evaluate whether this blacklist has a
   // non-NO_INFO status. If it does, we set the pref we evaluate in
   // GetFeatureStatus above, so we don't need to hold on to this blacklist
   // anywhere permanent.
   int i = 0;
--- a/widget/xpwidgets/GfxInfoBase.h
+++ b/widget/xpwidgets/GfxInfoBase.h
@@ -63,16 +63,22 @@ public:
   NS_IMETHOD_(void) GetData() { }
 
   static void AddCollector(GfxInfoCollectorBase* collector);
   static void RemoveCollector(GfxInfoCollectorBase* collector);
 
   static nsTArray<GfxDriverInfo>* mDriverInfo;
   static bool mDriverInfoObserverInitialized;
 
+  virtual const nsAString& Model() const { return nsString(); }
+  virtual const nsAString& Hardware() const { return nsString(); }
+  virtual const nsAString& Product() const { return nsString(); }
+  virtual const nsAString& Manufacturer() const { return nsString(); }
+  virtual uint32_t OperatingSystemVersion() const { return 0; }
+
 protected:
 
   virtual nsresult GetFeatureStatusImpl(int32_t aFeature, int32_t* aStatus,
                                         nsAString& aSuggestedDriverVersion,
                                         const nsTArray<GfxDriverInfo>& aDriverInfo,
                                         OperatingSystem* aOS = nullptr);
 
   // Gets the driver info table. Used by GfxInfoBase to check for general cases