Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 20 Dec 2013 11:37:10 +0100
changeset 171795 56d3a3f8bb7c41d0d999031738995018f766464a
parent 171794 4a1ec6b78880f225edffc6a3a90d696f4aab7ae9 (current diff)
parent 171625 c9ea463d36c3668459d24f5ca656dbc5489c195f (diff)
child 171796 57c244606b0240c14790341a4bdadab8017ef09b
push id5166
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:47:54 +0000
treeherdermozilla-aurora@977eb2548b2d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
Merge mozilla-central to mozilla-inbound
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "c8f56fb9f9888d4f4e7f55a52a639b9271b8d207", 
+    "revision": "6c2b33c8f522e2fa419011bc2de456c11e76b29f", 
     "repo_path": "/integration/gaia-central"
 }
--- a/docshell/base/nsIMarkupDocumentViewer.idl
+++ b/docshell/base/nsIMarkupDocumentViewer.idl
@@ -77,16 +77,28 @@ interface nsIMarkupDocumentViewer : nsIS
    * Set the maximum line width for the document.
    * NOTE: This will generate a reflow!
    *
    * @param maxLineWidth The maximum width of any line boxes on the page,
    *        in CSS pixels.
    */
   void changeMaxLineBoxWidth(in int32_t maxLineBoxWidth);
 
+  /**
+   * Instruct the refresh driver to discontinue painting until further
+   * notice.
+   */
+  void pausePainting();
+
+  /**
+   * Instruct the refresh driver to resume painting after a previous call to
+   * pausePainting().
+   */
+  void resumePainting();
+
   /*
    * Render the document as if being viewed on a device with the specified
    * media type. This will cause a reflow.
    *
    * @param mediaType The media type to be emulated
    */
   void emulateMedium(in AString aMediaType);
 
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -98,17 +98,22 @@ this.Keyboard = {
       }
 
       // That should never happen.
       if (!mm) {
         dump("!! No message manager found for " + msg.name);
         return;
       }
 
-      if (!mm.assertPermission("input")) {
+      let testing = false;
+      try {
+        testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing");
+      } catch (e) {
+      }
+      if (!testing && !mm.assertPermission("input")) {
         dump("Keyboard message " + msg.name +
         " from a content process with no 'input' privileges.");
         return;
       }
     }
 
     switch (msg.name) {
       case 'Forms:Input':
@@ -175,21 +180,19 @@ this.Keyboard = {
                              .frameLoader.messageManager;
 
     ppmm.broadcastAsyncMessage(newEventName, msg.data);
   },
 
   handleFocusChange: function keyboardHandleFocusChange(msg) {
     this.forwardEvent('Keyboard:FocusChange', msg);
 
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-
     // Chrome event, used also to render value selectors; that's why we need
     // the info about choices / min / max here as well...
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: 'inputmethod-contextchange',
       inputType: msg.data.type,
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
       min: msg.data.min,
       max: msg.data.max
     });
   },
@@ -214,25 +217,23 @@ this.Keyboard = {
     this.sendAsyncMessage('Forms:Select:Blur', {});
   },
 
   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
     this.sendAsyncMessage('Forms:ReplaceSurroundingText', msg.data);
   },
 
   showInputMethodPicker: function keyboardShowInputMethodPicker() {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: "inputmethod-showall"
     });
   },
 
   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: "inputmethod-next"
     });
   },
 
   getText: function keyboardGetText(msg) {
     this.sendAsyncMessage('Forms:GetText', msg.data);
   },
 
@@ -262,12 +263,19 @@ this.Keyboard = {
   _layouts: null,
   setLayouts: function keyboardSetLayoutCount(layouts) {
     // The input method plugins may not have loaded yet,
     // cache the layouts so on init we can respond immediately instead
     // of going back and forth between keyboard_manager
     this._layouts = layouts;
 
     ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', layouts);
+  },
+
+  sendChromeEvent: function(event) {
+    let browser = Services.wm.getMostRecentWindow("navigator:browser");
+    if (browser && browser.shell) {
+      browser.shell.sendChromeEvent(event);;
+    }
   }
 };
 
 this.Keyboard.init();
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/file_test_app.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<input id="test-input" type="text" value="Yuan" x-inputmode="verbatim" lang="zh"/>
+<script type="application/javascript;version=1.7">
+  let input = document.getElementById('test-input');
+  input.focus();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/inputmethod_common.js
@@ -0,0 +1,32 @@
+function inputmethod_setup(callback) {
+  SimpleTest.waitForExplicitFinish();
+  let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1']
+                .getService(SpecialPowers.Ci.nsIXULAppInfo);
+  if (appInfo.name != 'B2G') {
+    SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", this);
+  }
+
+  let permissions = [];
+  ['input-manage', 'browser'].forEach(function(name) {
+    permissions.push({
+      type: name,
+      allow: true,
+      context: document
+    });
+  });
+
+  SpecialPowers.pushPermissions(permissions, function() {
+    let prefs = [
+      ['dom.mozBrowserFramesEnabled', true],
+      // Enable navigator.mozInputMethod.
+      ['dom.mozInputMethod.enabled', true],
+      // Bypass the permission check for mozInputMethod API.
+      ['dom.mozInputMethod.testing', true]
+    ];
+    SpecialPowers.pushPrefEnv({set: prefs}, callback);
+  });
+}
+
+function inputmethod_cleanup() {
+  SimpleTest.finish();
+}
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+  inputmethod_common.js
+  file_test_app.html
+
+[test_basic.html]
+
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_basic.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=932145
+-->
+<head>
+  <title>Basic test for InputMethod API.</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932145">Mozilla Bug 932145</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+// The input context.
+var gContext = null;
+
+inputmethod_setup(function() {
+  runTest();
+});
+
+function runTest() {
+  let im = navigator.mozInputMethod;
+
+  im.oninputcontextchange = function() {
+    ok(true, 'inputcontextchange event was fired.');
+    im.oninputcontextchange = null;
+
+    gContext = im.inputcontext;
+    if (!gContext) {
+      ok(false, 'Should have a non-null inputcontext.');
+      inputmethod_cleanup();
+      return;
+    }
+
+    todo_is(gContext.type, 'input', 'The input context type should match.');
+    is(gContext.inputType, 'text', 'The inputType should match.');
+    is(gContext.inputMode, 'verbatim', 'The input mode should match.');
+    is(gContext.lang, 'zh', 'The language should match.');
+    is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan',
+       'Should get the text around the cursor.');
+
+    test_setSelectionRange();
+  };
+
+  // Set current page as an input method.
+  SpecialPowers.wrap(im).setActive(true);
+
+  let iframe = document.createElement('iframe');
+  iframe.src = 'file_test_app.html';
+  iframe.setAttribute('mozbrowser', true);
+  document.body.appendChild(iframe);
+}
+
+function test_setSelectionRange() {
+  // Move cursor position to 2.
+  gContext.setSelectionRange(2, 0).then(function() {
+    is(gContext.selectionStart, 2, 'selectionStart was set successfully.');
+    is(gContext.selectionEnd, 2, 'selectionEnd was set successfully.');
+    test_sendKey();
+  }, function(e) {
+    ok(false, 'setSelectionRange failed:' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_sendKey() {
+  // Add '-' to current cursor posistion and move the cursor position to 3.
+  gContext.sendKey(0, '-'.charCodeAt(0), 0).then(function() {
+    is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yu-an',
+       'sendKey should changed the input field correctly.');
+    test_deleteSurroundingText();
+  }, function(e) {
+    ok(false, 'sendKey failed:' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_deleteSurroundingText() {
+  // Remove one character before current cursor position and move the cursor
+  // position back to 2.
+  gContext.deleteSurroundingText(1, 0).then(function() {
+    ok(true, 'deleteSurroundingText finished');
+    is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan',
+       'deleteSurroundingText should changed the input field correctly.');
+    test_replaceSurroundingText();
+  }, function(e) {
+    ok(false, 'deleteSurroundingText failed:' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_replaceSurroundingText() {
+  // Replace 'Yuan' with 'Xulei'.
+  gContext.replaceSurroundingText('Xulei', 2, 2).then(function() {
+    ok(true, 'replaceSurroundingText finished');
+    is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei',
+       'replaceSurroundingText changed the input field correctly.');
+    test_setComposition();
+  }, function(e) {
+    ok(false, 'replaceSurroundingText failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_setComposition() {
+  gContext.setComposition('XXX').then(function() {
+    ok(true, 'setComposition finished');
+    test_endComposition();
+  }, function(e) {
+    ok(false, 'setComposition failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_endComposition() {
+  gContext.endComposition('2013').then(function() {
+    if (gContext.textBeforeCursor + gContext.textAfterCursor == 'Xulei2013') {
+      ok(true, 'endComposition changed the input field correctly.');
+    } else {
+      todo(false, 'endComposition changed the input field incorrectly.');
+    }
+    inputmethod_cleanup();
+  }, function (e) {
+    ok(false, 'endComposition failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/inputmethod/moz.build
+++ b/dom/inputmethod/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+TEST_DIRS += ['mochitest']
+
 XPIDL_SOURCES += [
     'nsIB2GKeyboard.idl',
 ]
 
 XPIDL_MODULE = 'dom_inputmethod'
 
 EXTRA_COMPONENTS += [
     'InputMethod.manifest',
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -1048,16 +1048,19 @@ xpc::CreateSandboxObject(JSContext *cx, 
         MOZ_ASSERT(principal);
     }
 
     JS::CompartmentOptions compartmentOptions;
     if (options.sameZoneAs)
         compartmentOptions.setSameZoneAs(js::UncheckedUnwrap(options.sameZoneAs));
     else
         compartmentOptions.setZone(JS::SystemZone);
+
+    compartmentOptions.setInvisibleToDebugger(options.invisibleToDebugger);
+
     RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass,
                                                      principal, compartmentOptions));
     if (!sandbox)
         return NS_ERROR_FAILURE;
 
     // Set up the wantXrays flag, which indicates whether xrays are desired even
     // for same-origin access.
     //
@@ -1481,16 +1484,17 @@ bool
 SandboxOptions::Parse()
 {
     return ParseObject("sandboxPrototype", &proto) &&
            ParseBoolean("wantXrays", &wantXrays) &&
            ParseBoolean("wantComponents", &wantComponents) &&
            ParseBoolean("wantExportHelpers", &wantExportHelpers) &&
            ParseString("sandboxName", sandboxName) &&
            ParseObject("sameZoneAs", &sameZoneAs) &&
+           ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
            ParseGlobalProperties() &&
            ParseValue("metadata", &metadata);
 }
 
 static nsresult
 AssembleSandboxMemoryReporterName(JSContext *cx, nsCString &sandboxName)
 {
     // Use a default name when the caller did not provide a sandboxName.
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3427,27 +3427,29 @@ public:
     SandboxOptions(JSContext *cx = xpc_GetSafeJSContext(),
                    JSObject *options = nullptr)
         : OptionsBase(cx, options)
         , wantXrays(true)
         , wantComponents(true)
         , wantExportHelpers(false)
         , proto(cx)
         , sameZoneAs(cx)
+        , invisibleToDebugger(false)
         , metadata(cx)
     { }
 
     virtual bool Parse();
 
     bool wantXrays;
     bool wantComponents;
     bool wantExportHelpers;
     JS::RootedObject proto;
     nsCString sandboxName;
     JS::RootedObject sameZoneAs;
+    bool invisibleToDebugger;
     GlobalProperties globalProperties;
     JS::RootedValue metadata;
 
 protected:
     bool ParseGlobalProperties();
 };
 
 class MOZ_STACK_CLASS CreateObjectInOptions : public OptionsBase {
--- a/js/xpconnect/tests/chrome/test_evalInSandbox.xul
+++ b/js/xpconnect/tests/chrome/test_evalInSandbox.xul
@@ -145,10 +145,31 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       try {
         var sandbox = new Cu.Sandbox(this, { sameZoneAs: this } );
         ok(true, "sameZoneAs works");
       }
       catch (e) {
         ok(false, "sameZoneAs works");
       }
+
+      Cu.import("resource://gre/modules/jsdebugger.jsm");
+      addDebuggerToGlobal(this);
+
+      try {
+        let dbg = new Debugger();
+        let sandbox = new Cu.Sandbox(this, { invisibleToDebugger: false });
+        dbg.addDebuggee(sandbox);
+        ok(true, "debugger added visible value");
+      } catch(e) {
+        ok(false, "debugger could not add visible value");
+      }
+
+      try {
+        let dbg = new Debugger();
+        let sandbox = new Cu.Sandbox(this, { invisibleToDebugger: true });
+        dbg.addDebuggee(sandbox);
+        ok(false, "debugger added invisible value");
+      } catch(e) {
+        ok(true, "debugger did not add invisible value");
+      }
   ]]></script>
 </window>
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2696,16 +2696,27 @@ nsDocumentViewer::CallChildren(CallChild
 }
 
 struct LineBoxInfo
 {
   nscoord mMaxLineBoxWidth;
 };
 
 static void
+ChangeChildPaintingEnabled(nsIMarkupDocumentViewer* aChild, void* aClosure)
+{
+  bool* enablePainting = (bool*) aClosure;
+  if (*enablePainting) {
+    aChild->ResumePainting();
+  } else {
+    aChild->PausePainting();
+  }
+}
+
+static void
 ChangeChildMaxLineBoxWidth(nsIMarkupDocumentViewer* aChild, void* aClosure)
 {
   struct LineBoxInfo* lbi = (struct LineBoxInfo*) aClosure;
   aChild->ChangeMaxLineBoxWidth(lbi->mMaxLineBoxWidth);
 }
 
 struct ZoomInfo
 {
@@ -3121,17 +3132,46 @@ AppendChildSubtree(nsIMarkupDocumentView
 
 NS_IMETHODIMP nsDocumentViewer::AppendSubtree(nsTArray<nsCOMPtr<nsIMarkupDocumentViewer> >& aArray)
 {
   aArray.AppendElement(this);
   CallChildren(AppendChildSubtree, &aArray);
   return NS_OK;
 }
 
-NS_IMETHODIMP nsDocumentViewer::ChangeMaxLineBoxWidth(int32_t aMaxLineBoxWidth)
+NS_IMETHODIMP
+nsDocumentViewer::PausePainting()
+{
+  bool enablePaint = false;
+  CallChildren(ChangeChildPaintingEnabled, &enablePaint);
+
+  nsIPresShell* presShell = GetPresShell();
+  if (presShell) {
+    presShell->PausePainting();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ResumePainting()
+{
+  bool enablePaint = true;
+  CallChildren(ChangeChildPaintingEnabled, &enablePaint);
+
+  nsIPresShell* presShell = GetPresShell();
+  if (presShell) {
+    presShell->ResumePainting();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ChangeMaxLineBoxWidth(int32_t aMaxLineBoxWidth)
 {
   // Change the max line box width for all children.
   struct LineBoxInfo lbi = { aMaxLineBoxWidth };
   CallChildren(ChangeChildMaxLineBoxWidth, &lbi);
 
   // Now, change our max line box width.
   // Convert to app units, since our input is in CSS pixels.
   nscoord mlbw = nsPresContext::CSSPixelsToAppUnits(aMaxLineBoxWidth);
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -124,20 +124,20 @@ typedef struct CapturingContentInfo {
   bool mAllowed;
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   nsIContent* mContent;
 } CapturingContentInfo;
 
 
-// f5b542a9-eaf0-4560-a656-37a9d379864c
+// 0e4f2b36-7ab8-43c5-b912-5c311566297c
 #define NS_IPRESSHELL_IID \
-{ 0xf5b542a9, 0xeaf0, 0x4560, \
-  { 0x37, 0xa9, 0xd3, 0x79, 0x86, 0x4c } }
+{ 0xde498c49, 0xf83f, 0x47bf, \
+  {0x8c, 0xc6, 0x8f, 0xf8, 0x74, 0x62, 0x22, 0x23 } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -832,16 +832,30 @@ public:
   /**
    * Called to find out if painting is suppressed for this presshell.  If it is suppressd,
    * we don't allow the painting of any layer but the background, and we don't
    * recur into our children.
    */
   bool IsPaintingSuppressed() const { return mPaintingSuppressed; }
 
   /**
+   * Pause painting by freezing the refresh driver of this and all parent
+   * presentations. This may not have the desired effect if this pres shell
+   * has its own refresh driver.
+   */
+  virtual void PausePainting() = 0;
+
+  /**
+   * Resume painting by thawing the refresh driver of this and all parent
+   * presentations. This may not have the desired effect if this pres shell
+   * has its own refresh driver.
+   */
+  virtual void ResumePainting() = 0;
+
+  /**
    * Unsuppress painting.
    */
   virtual NS_HIDDEN_(void) UnsuppressPainting() = 0;
 
   /**
    * Called to disable nsITheme support in a specific presshell.
    */
   void DisableThemeSupport()
@@ -1596,16 +1610,17 @@ protected:
   // Cached font inflation values. This is done to prevent changing of font
   // inflation until a page is reloaded.
   uint32_t mFontSizeInflationEmPerLine;
   uint32_t mFontSizeInflationMinTwips;
   uint32_t mFontSizeInflationLineThreshold;
   bool mFontSizeInflationForceEnabled;
   bool mFontSizeInflationDisabledInMasterProcess;
   bool mFontSizeInflationEnabled;
+  bool mPaintingIsFrozen;
 
   // Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
   bool mFontSizeInflationEnabledIsDirty;
 
   // Flag to indicate whether or not there is a reflow on zoom event pending.
   // See IsReflowOnZoomPending() for more information.
   bool mReflowOnZoomPending;
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -719,16 +719,18 @@ PresShell::PresShell()
   mMaxLineBoxWidth = 0;
 
   static bool addedSynthMouseMove = false;
   if (!addedSynthMouseMove) {
     Preferences::AddBoolVarCache(&sSynthMouseMove,
                                  "layout.reflow.synthMouseMove", true);
     addedSynthMouseMove = true;
   }
+
+  mPaintingIsFrozen = false;
 }
 
 NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver,
                    nsISelectionController,
                    nsISelectionDisplay, nsIObserver, nsISupportsWeakReference,
                    nsIMutationObserver)
 
 PresShell::~PresShell()
@@ -739,16 +741,23 @@ PresShell::~PresShell()
   }
 
   NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
                "Huh, event content left on the stack in pres shell dtor!");
   NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
                mLastCallbackEventRequest == nullptr,
                "post-reflow queues not empty.  This means we're leaking");
 
+  // Verify that if painting was frozen, but we're being removed from the tree,
+  // that we now re-enable painting on our refresh driver, since it may need to
+  // be re-used by another presentation.
+  if (mPaintingIsFrozen) {
+    mPresContext->RefreshDriver()->Thaw();
+  }
+
 #ifdef DEBUG
   MOZ_ASSERT(mPresArenaAllocCount == 0,
              "Some pres arena objects were not freed");
 #endif
 
   delete mStyleSet;
   delete mFrameConstructor;
 
@@ -9926,8 +9935,28 @@ nsIPresShell::SetMaxLineBoxWidth(nscoord
   NS_ASSERTION(aMaxLineBoxWidth >= 0, "attempting to set max line box width to a negative value");
 
   if (mMaxLineBoxWidth != aMaxLineBoxWidth) {
     mMaxLineBoxWidth = aMaxLineBoxWidth;
     mReflowOnZoomPending = true;
     FrameNeedsReflow(GetRootFrame(), eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 }
+
+void
+PresShell::PausePainting()
+{
+  if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
+    return;
+
+  mPaintingIsFrozen = true;
+  GetPresContext()->RefreshDriver()->Freeze();
+}
+
+void
+PresShell::ResumePainting()
+{
+  if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
+    return;
+
+  mPaintingIsFrozen = false;
+  GetPresContext()->RefreshDriver()->Thaw();
+}
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -684,16 +684,19 @@ protected:
   bool ScheduleReflowOffTimer();
 
   // Widget notificiations
   virtual void WindowSizeMoveDone() MOZ_OVERRIDE;
   virtual void SysColorChanged() MOZ_OVERRIDE { mPresContext->SysColorChanged(); }
   virtual void ThemeChanged() MOZ_OVERRIDE { mPresContext->ThemeChanged(); }
   virtual void BackingScaleFactorChanged() MOZ_OVERRIDE { mPresContext->UIResolutionChanged(); }
 
+  virtual void PausePainting() MOZ_OVERRIDE;
+  virtual void ResumePainting() MOZ_OVERRIDE;
+
   void UpdateImageVisibility();
 
   nsRevocableEventPtr<nsRunnableMethod<PresShell> > mUpdateImageVisibilityEvent;
 
   void ClearVisibleImagesList();
   static void ClearImageVisibilityVisited(nsView* aView, bool aClear);
   static void MarkImagesInListVisible(const nsDisplayList& aList);
 
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -240,41 +240,49 @@ class JavaPanZoomController
     public void handleMessage(String event, JSONObject message) {
         try {
             if (MESSAGE_ZOOM_RECT.equals(event)) {
                 float x = (float)message.getDouble("x");
                 float y = (float)message.getDouble("y");
                 final RectF zoomRect = new RectF(x, y,
                                      x + (float)message.getDouble("w"),
                                      y + (float)message.getDouble("h"));
-                mTarget.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        animatedZoomTo(zoomRect);
-                    }
-                });
+                if (message.optBoolean("animate", true)) {
+                    mTarget.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            animatedZoomTo(zoomRect);
+                        }
+                    });
+                } else {
+                    mTarget.setViewportMetrics(getMetricsToZoomTo(zoomRect));
+                }
             } else if (MESSAGE_ZOOM_PAGE.equals(event)) {
                 ImmutableViewportMetrics metrics = getMetrics();
                 RectF cssPageRect = metrics.getCssPageRect();
 
                 RectF viewableRect = metrics.getCssViewport();
                 float y = viewableRect.top;
                 // attempt to keep zoom keep focused on the center of the viewport
                 float newHeight = viewableRect.height() * cssPageRect.width() / viewableRect.width();
                 float dh = viewableRect.height() - newHeight; // increase in the height
                 final RectF r = new RectF(0.0f,
                                     y + dh/2,
                                     cssPageRect.width(),
                                     y + dh/2 + newHeight);
-                mTarget.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        animatedZoomTo(r);
-                    }
-                });
+                if (message.optBoolean("animate", true)) {
+                    mTarget.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            animatedZoomTo(r);
+                        }
+                    });
+                } else {
+                    mTarget.setViewportMetrics(getMetricsToZoomTo(r));
+                }
             } else if (MESSAGE_TOUCH_LISTENER.equals(event)) {
                 int tabId = message.getInt("tabID");
                 final Tab tab = Tabs.getInstance().getTab(tabId);
                 tab.setHasTouchListeners(true);
                 mTarget.post(new Runnable() {
                     @Override
                     public void run() {
                         if (Tabs.getInstance().isSelectedTab(tab))
@@ -1394,17 +1402,17 @@ class JavaPanZoomController
     }
 
     /**
      * Zoom to a specified rect IN CSS PIXELS.
      *
      * While we usually use device pixels, @zoomToRect must be specified in CSS
      * pixels.
      */
-    private boolean animatedZoomTo(RectF zoomToRect) {
+    private ImmutableViewportMetrics getMetricsToZoomTo(RectF zoomToRect) {
         final float startZoom = getMetrics().zoomFactor;
 
         RectF viewport = getMetrics().getViewport();
         // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport,
         // enlarging as necessary (if it gets too big, it will get shrunk in the next step).
         // while enlarging make sure we enlarge equally on both sides to keep the target rect
         // centered.
         float targetRatio = viewport.width() / viewport.height();
@@ -1429,18 +1437,21 @@ class JavaPanZoomController
         finalMetrics = finalMetrics.setViewportOrigin(
             zoomToRect.left * finalMetrics.zoomFactor,
             zoomToRect.top * finalMetrics.zoomFactor);
         finalMetrics = finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
 
         // 2. now run getValidViewportMetrics on it, so that the target viewport is
         // clamped down to prevent overscroll, over-zoom, and other bad conditions.
         finalMetrics = getValidViewportMetrics(finalMetrics);
+        return finalMetrics;
+    }
 
-        bounce(finalMetrics, PanZoomState.ANIMATED_ZOOM);
+    private boolean animatedZoomTo(RectF zoomToRect) {
+        bounce(getMetricsToZoomTo(zoomToRect), PanZoomState.ANIMATED_ZOOM);
         return true;
     }
 
     /** This function must be called from the UI thread. */
     @Override
     public void abortPanning() {
         checkMainThread();
         bounce();
--- a/mobile/android/base/tests/helpers/WaitHelper.java
+++ b/mobile/android/base/tests/helpers/WaitHelper.java
@@ -10,16 +10,18 @@ import org.mozilla.gecko.Actions;
 import org.mozilla.gecko.Actions.EventExpecter;
 import org.mozilla.gecko.tests.components.ToolbarComponent;
 import org.mozilla.gecko.tests.UITestContext;
 import org.mozilla.gecko.tests.UITestContext.ComponentType;
 
 import com.jayway.android.robotium.solo.Condition;
 import com.jayway.android.robotium.solo.Solo;
 
+import java.util.regex.Pattern;
+
 /**
  * Provides functionality related to waiting on certain events to happen.
  */
 public final class WaitHelper {
     // TODO: Make public for when Solo.waitForCondition is used directly (i.e. do not want
     // assertion from waitFor)?
     private static final int DEFAULT_MAX_WAIT_MS = 5000;
     private static final int PAGE_LOAD_WAIT_MS = 10000;
@@ -126,17 +128,17 @@ public final class WaitHelper {
         public boolean hasStateChanged();
     }
 
     private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier {
         private static final String LOGTAG =
                 ToolbarTitleTextChangeVerifier.class.getSimpleName() + ": ";
 
         // A regex that matches the page title that shows up while the page is loading.
-        private static final String LOADING_REGEX = "^[A-Za-z]{3,9}://";
+        private static final Pattern LOADING_PREFIX = Pattern.compile("[A-Za-z]{3,9}://");
 
         private CharSequence mOldTitleText;
 
         @Override
         public String getLogTag() {
             return LOGTAG;
         }
 
@@ -152,17 +154,17 @@ public final class WaitHelper {
             // TODO: Robocop sleeps .5 sec between calls. Cache title view?
             final CharSequence title = sToolbar.getPotentiallyInconsistentTitle();
 
             // TODO: Handle the case where the URL is shown instead of page title by preference.
             // HACK: We want to wait until the title changes to the state a tester may assert
             // (e.g. the page title). However, the title is set to the URL before the title is
             // loaded from the server and set as the final page title; we ignore the
             // intermediate URL loading state here.
-            final boolean isLoading = title.toString().matches(LOADING_REGEX);
+            final boolean isLoading = LOADING_PREFIX.matcher(title).lookingAt();
             final boolean hasStateChanged = !isLoading && !mOldTitleText.equals(title);
 
             if (hasStateChanged) {
                 sContext.dumpLog(LOGTAG + "state changed to title, \"" + title + "\".");
             }
             return hasStateChanged;
         }
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -173,20 +173,30 @@ function doChangeMaxLineBoxWidth(aWidth)
   let docShell = webNav.QueryInterface(Ci.nsIDocShell);
   let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
 
   let range = null;
   if (BrowserApp.selectedTab._mReflozPoint) {
     range = BrowserApp.selectedTab._mReflozPoint.range;
   }
 
-  docViewer.changeMaxLineBoxWidth(aWidth);
-
-  if (range) {
-    BrowserEventHandler._zoomInAndSnapToRange(range);
+  try {
+    docViewer.pausePainting();
+    docViewer.changeMaxLineBoxWidth(aWidth);
+
+    if (range) {
+      BrowserEventHandler._zoomInAndSnapToRange(range);
+    } else {
+      // In this case, we actually didn't zoom into a specific range. It
+      // probably happened from a page load reflow-on-zoom event, so we
+      // need to make sure painting is re-enabled.
+      BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
+    }
+  } finally {
+    docViewer.resumePainting();
   }
 }
 
 function fuzzyEquals(a, b) {
   return (Math.abs(a - b) < 1e-6);
 }
 
 /**
@@ -2739,16 +2749,17 @@ Tab.prototype = {
     this.browser.addEventListener("scroll", this, true);
     this.browser.addEventListener("MozScrolledAreaChanged", this, true);
     // Note that the XBL binding is untrusted
     this.browser.addEventListener("PluginBindingAttached", this, true, true);
     this.browser.addEventListener("pageshow", this, true);
     this.browser.addEventListener("MozApplicationManifest", this, true);
 
     Services.obs.addObserver(this, "before-first-paint", false);
+    Services.obs.addObserver(this, "after-viewport-change", false);
     Services.prefs.addObserver("browser.ui.zoom.force-user-scalable", this, false);
 
     if (aParams.delayLoad) {
       // If this is a zombie tab, attach restore data so the tab will be
       // restored when selected
       this.browser.__SS_data = {
         entries: [{
           url: aURL,
@@ -2798,31 +2809,68 @@ Tab.prototype = {
     // We only use the font.size.inflation.minTwips preference because this is
     // the only one that is controlled by the user-interface in the 'Settings'
     // menu. Thus, if font.size.inflation.emPerLine is changed, this does not
     // effect reflow-on-zoom.
     let minFontSize = convertFromTwipsToPx(Services.prefs.getIntPref("font.size.inflation.minTwips"));
     return minFontSize / this.getInflatedFontSizeFor(aElement);
   },
 
+  clearReflowOnZoomPendingActions: function() {
+    // Reflow was completed, so now re-enable painting.
+    let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+    let docShell = webNav.QueryInterface(Ci.nsIDocShell);
+    let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+    docViewer.resumePainting();
+
+    BrowserApp.selectedTab._mReflozPositioned = false;
+  },
+
+  /**
+   * Reflow on zoom consists of a few different sub-operations:
+   *
+   * 1. When a double-tap event is seen, we verify that the correct preferences
+   *    are enabled and perform the pre-position handling calculation. We also
+   *    signal that reflow-on-zoom should be performed at this time, and pause
+   *    painting.
+   * 2. During the next call to setViewport(), which is in the Tab prototype,
+   *    we detect that a call to changeMaxLineBoxWidth should be performed. If
+   *    we're zooming out, then the max line box width should be reset at this
+   *    time. Otherwise, we call performReflowOnZoom.
+   *   2a. PerformReflowOnZoom() and resetMaxLineBoxWidth() schedule a call to
+   *       doChangeMaxLineBoxWidth, based on a timeout specified in preferences.
+   * 3. doChangeMaxLineBoxWidth changes the line box width (which also
+   *    schedules a reflow event), and then calls _zoomInAndSnapToRange.
+   * 4. _zoomInAndSnapToRange performs the positioning of reflow-on-zoom and
+   *    then re-enables painting.
+   *
+   * Some of the events happen synchronously, while others happen asynchronously.
+   * The following is a rough sketch of the progression of events:
+   *
+   * double tap event seen -> onDoubleTap() -> ... asynchronous ...
+   *   -> setViewport() -> performReflowOnZoom() -> ... asynchronous ...
+   *   -> doChangeMaxLineBoxWidth() -> _zoomInAndSnapToRange()
+   *   -> ... asynchronous ... -> setViewport() -> Observe('after-viewport-change')
+   *   -> resumePainting()
+   */
   performReflowOnZoom: function(aViewport) {
-      let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
-
-      let viewportWidth = gScreenWidth / zoom;
-      let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
-
-      if (gReflowPending) {
-        clearTimeout(gReflowPending);
-      }
-
-      // We add in a bit of fudge just so that the end characters
-      // don't accidentally get clipped. 15px is an arbitrary choice.
-      gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
-                                  reflozTimeout,
-                                  viewportWidth - 15);
+    let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
+
+    let viewportWidth = gScreenWidth / zoom;
+    let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
+
+    if (gReflowPending) {
+      clearTimeout(gReflowPending);
+    }
+
+    // We add in a bit of fudge just so that the end characters
+    // don't accidentally get clipped. 15px is an arbitrary choice.
+    gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
+                                reflozTimeout,
+                                viewportWidth - 15);
   },
 
   /** 
    * Reloads the tab with the desktop mode setting.
    */
   reloadWithMode: function (aDesktopMode) {
     // Set desktop mode for tab and send change to Java
     if (this.desktopMode != aDesktopMode) {
@@ -2884,16 +2932,17 @@ Tab.prototype = {
     this.browser.removeEventListener("blur", this, true);
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
     this.browser.removeEventListener("PluginBindingAttached", this, true);
     this.browser.removeEventListener("pageshow", this, true);
     this.browser.removeEventListener("MozApplicationManifest", this, true);
 
     Services.obs.removeObserver(this, "before-first-paint");
+    Services.obs.removeObserver(this, "after-viewport-change");
     Services.prefs.removeObserver("browser.ui.zoom.force-user-scalable", this);
 
     // Make sure the previously selected panel remains selected. The selected panel of a deck is
     // not stable when panels are removed.
     let selectedPanel = BrowserApp.deck.selectedPanel;
     BrowserApp.deck.removeChild(this.browser);
     BrowserApp.deck.selectedPanel = selectedPanel;
 
@@ -3170,23 +3219,31 @@ Tab.prototype = {
       // because we are pinch-zooming to zoom out.
       BrowserEventHandler.resetMaxLineBoxWidth();
       BrowserApp.selectedTab.reflozPinchSeen = false;
     } else if (BrowserApp.selectedTab.reflozPinchSeen &&
                isZooming) {
       // In this case, the user pinch-zoomed in, so we don't want to
       // preserve position as we would with reflow-on-zoom.
       BrowserApp.selectedTab.probablyNeedRefloz = false;
+      BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
       BrowserApp.selectedTab._mReflozPoint = null;
     }
 
+    let docViewer = null;
+
     if (isZooming &&
         BrowserEventHandler.mReflozPref &&
         BrowserApp.selectedTab._mReflozPoint &&
         BrowserApp.selectedTab.probablyNeedRefloz) {
+      let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+      let docShell = webNav.QueryInterface(Ci.nsIDocShell);
+      docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+      docViewer.pausePainting();
+
       BrowserApp.selectedTab.performReflowOnZoom(aViewport);
       BrowserApp.selectedTab.probablyNeedRefloz = false;
     }
 
     let win = this.browser.contentWindow;
     win.scrollTo(x, y);
 
     this.userScrollPos.x = win.scrollX;
@@ -3205,16 +3262,19 @@ Tab.prototype = {
     let dwi = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     dwi.setContentDocumentFixedPositionMargins(
       aViewport.fixedMarginTop / aViewport.zoom,
       aViewport.fixedMarginRight / aViewport.zoom,
       aViewport.fixedMarginBottom / aViewport.zoom,
       aViewport.fixedMarginLeft / aViewport.zoom);
 
     Services.obs.notifyObservers(null, "after-viewport-change", "");
+    if (docViewer) {
+        docViewer.resumePainting();
+    }
   },
 
   setResolution: function(aZoom, aForce) {
     // Set zoom level
     if (aForce || !fuzzyEquals(aZoom, this._zoom)) {
       this._zoom = aZoom;
       if (BrowserApp.selectedTab == this) {
         let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
@@ -4161,16 +4221,21 @@ Tab.prototype = {
 
         if (rzEnabled && rzPl) {
           // Retrieve the viewport width and adjust the max line box width
           // accordingly.
           let vp = BrowserApp.selectedTab.getViewport();
           BrowserApp.selectedTab.performReflowOnZoom(vp);
         }
         break;
+      case "after-viewport-change":
+        if (BrowserApp.selectedTab._mReflozPositioned) {
+          BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
+        }
+        break;
       case "nsPref:changed":
         if (aData == "browser.ui.zoom.force-user-scalable")
           ViewportHandler.updateMetadata(this, false);
         break;
     }
   },
 
   set readerEnabled(isReaderEnabled) {
@@ -4479,23 +4544,33 @@ var BrowserEventHandler = {
     let element = ElementTouchHelper.anyElementFromPoint(data.x, data.y);
 
     // We only want to do this if reflow-on-zoom is enabled, we don't already
     // have a reflow-on-zoom event pending, and the element upon which the user
     // double-tapped isn't of a type we want to avoid reflow-on-zoom.
     if (BrowserEventHandler.mReflozPref &&
        !BrowserApp.selectedTab._mReflozPoint &&
        !this._shouldSuppressReflowOnZoom(element)) {
-     let data = JSON.parse(aData);
-     let zoomPointX = data.x;
-     let zoomPointY = data.y;
-
-     BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
-       range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
-       BrowserApp.selectedTab.probablyNeedRefloz = true;
+
+      // See comment above performReflowOnZoom() for a detailed description of
+      // the events happening in the reflow-on-zoom operation.
+      let data = JSON.parse(aData);
+      let zoomPointX = data.x;
+      let zoomPointY = data.y;
+
+      BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
+        range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
+
+      // Before we perform a reflow on zoom, let's disable painting.
+      let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+      let docShell = webNav.QueryInterface(Ci.nsIDocShell);
+      let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+      docViewer.pausePainting();
+
+      BrowserApp.selectedTab.probablyNeedRefloz = true;
     }
 
     if (!element) {
       this._zoomOut();
       return;
     }
 
     while (element && !this._shouldZoomToElement(element))
@@ -4585,21 +4660,17 @@ var BrowserEventHandler = {
     if (rect.w > viewport.cssWidth || rect.h > viewport.cssHeight) {
       BrowserEventHandler.resetMaxLineBoxWidth();
     }
 
     sendMessageToJava(rect);
   },
 
   _zoomInAndSnapToRange: function(aRange) {
-    if (!aRange) {
-      Cu.reportError("aRange is null in zoomInAndSnapToRange. Unable to maintain position.");
-      return;
-    }
-
+    // aRange is always non-null here, since a check happened previously.
     let viewport = BrowserApp.selectedTab.getViewport();
     let fudge = 15; // Add a bit of fudge.
     let boundingElement = aRange.offsetNode;
     while (!boundingElement.getBoundingClientRect && boundingElement.parentNode) {
       boundingElement = boundingElement.parentNode;
     }
 
     let rect = ElementTouchHelper.getBoundingContentRect(boundingElement);
@@ -4612,40 +4683,43 @@ var BrowserEventHandler = {
     // center the area of interest on the screen.
     let topPos = scrollTop + drRect.top - (viewport.cssHeight / 2.0);
 
     // Factor in the border and padding
     let boundingStyle = window.getComputedStyle(boundingElement);
     let leftAdjustment = parseInt(boundingStyle.paddingLeft) +
                          parseInt(boundingStyle.borderLeftWidth);
 
+    BrowserApp.selectedTab._mReflozPositioned = true;
+
     rect.type = "Browser:ZoomToRect";
     rect.x = Math.max(viewport.cssPageLeft, rect.x  - fudge + leftAdjustment);
     rect.y = Math.max(topPos, viewport.cssPageTop);
     rect.w = viewport.cssWidth;
     rect.h = viewport.cssHeight;
+    rect.animate = false;
 
     sendMessageToJava(rect);
     BrowserApp.selectedTab._mReflozPoint = null;
-   },
-
-   onPinchFinish: function(aData) {
-     let data = {};
-     try {
-       data = JSON.parse(aData);
-     } catch(ex) {
-       console.log(ex);
-       return;
-     }
-
-     if (BrowserEventHandler.mReflozPref &&
-         data.zoomDelta < 0.0) {
-       BrowserEventHandler.resetMaxLineBoxWidth();
-     }
-   },
+  },
+
+  onPinchFinish: function(aData) {
+    let data = {};
+    try {
+      data = JSON.parse(aData);
+    } catch(ex) {
+      console.log(ex);
+      return;
+    }
+
+    if (BrowserEventHandler.mReflozPref &&
+        data.zoomDelta < 0.0) {
+      BrowserEventHandler.resetMaxLineBoxWidth();
+    }
+  },
 
   _shouldZoomToElement: function(aElement) {
     let win = aElement.ownerDocument.defaultView;
     if (win.getComputedStyle(aElement, null).display == "inline")
       return false;
     if (aElement instanceof Ci.nsIDOMHTMLLIElement)
       return false;
     if (aElement instanceof Ci.nsIDOMHTMLQuoteElement)
--- a/testing/mochitest/android.json
+++ b/testing/mochitest/android.json
@@ -133,16 +133,17 @@
  "dom/indexedDB/ipc/test_ipc.html": "bug 783513",
  "dom/indexedDB/test/test_third_party.html": "TIMED_OUT",
  "dom/indexedDB/test/test_event_propagation.html": "TIMED_OUT, bug 780855",
  "dom/indexedDB/test/test_app_isolation_inproc.html": "TIMED_OUT",
  "dom/indexedDB/test/test_app_isolation_oop.html": "TIMED_OUT",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_inproc.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_oop.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_oop_inproc.html": "No test app installed",
+ "dom/inputmethod": "Not supported on Android",
  "dom/network/tests/test_network_basics.html": "",
  "dom/permission/tests/test_permission_basics.html": "",
  "dom/mobilemessage/tests/test_sms_basics.html": "Bug 909036",
  "dom/media/tests/ipc/test_ipc.html":"bug 910661",
  "dom/tests/mochitest/ajax/jquery/test_jQuery.html": "bug 775227",
  "dom/tests/mochitest/ajax/offline/test_simpleManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_updatingManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_xhtmlManifest.xhtml": "TIMED_OUT",
--- a/testing/mochitest/androidx86.json
+++ b/testing/mochitest/androidx86.json
@@ -210,16 +210,17 @@
  "dom/indexedDB/ipc/test_ipc.html": "bug 783513",
  "dom/indexedDB/test/test_third_party.html": "TIMED_OUT",
  "dom/indexedDB/test/test_event_propagation.html": "TIMED_OUT, bug 780855",
  "dom/indexedDB/test/test_app_isolation_inproc.html": "TIMED_OUT",
  "dom/indexedDB/test/test_app_isolation_oop.html": "TIMED_OUT",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_inproc.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_oop.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_oop_inproc.html": "No test app installed",
+ "dom/inputmethod": "Not supported on Android",
  "dom/media/tests/ipc/test_ipc.html": "x86 only bug 936226",
  "dom/network/tests/test_network_basics.html": "",
  "dom/permission/tests/test_permission_basics.html": "",
  "dom/mobilemessage/tests/test_sms_basics.html": "Bug 909036",
  "dom/tests/mochitest/ajax/jquery/test_jQuery.html": "bug 775227",
  "dom/tests/mochitest/ajax/offline/test_simpleManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_updatingManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_xhtmlManifest.xhtml": "TIMED_OUT",