Merge m-i to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 13 Oct 2013 10:18:40 -0700
changeset 164419 211337f7fb83f9549096d7ecbc95649103605bbb
parent 164405 cd87799152880df2227c14be2680d27e101ceccf (current diff)
parent 164418 e628236f3424d717d1fb846358a3368518552798 (diff)
child 164421 20ffeada8ecab8a70699718cbc5c7d370cd7f5bf
child 164428 64ae967e7b697f681c53001c49ec58f6b5c6cd09
child 164443 a4c2df11b142bbca37f7fd5e878347a00fce8b3e
child 170422 f6aa6fd8955e2c9a25aa2c3f0936e973fbcd7b00
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
211337f7fb83 / 27.0a1 / 20131014030204 / files
nightly linux64
211337f7fb83 / 27.0a1 / 20131014030204 / files
nightly mac
211337f7fb83 / 27.0a1 / 20131014030204 / files
nightly win32
211337f7fb83 / 27.0a1 / 20131014030204 / files
nightly win64
211337f7fb83 / 27.0a1 / 20131014030204 / 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 m-i to m-c
toolkit/modules/moz.build
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2673,40 +2673,34 @@ nsDocument::InitCSP(nsIChannel* aChannel
 
   // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
     // Use the 1.0 CSP parser for apps if the pref to do so is set.
     csp->AppendPolicy(appManifestCSP, selfURI, false, specCompliantEnabled);
   }
 
   // While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers
-  // can coexist with x- headers.  If both exist, they're both enforced, but
-  // there's a warning posted in the web console that the x-headers are going
-  // away.
+  // take priority.  If both are present, the x-* headers are ignored.
 
   // ----- if there's a full-strength CSP header, apply it.
-  if (!cspOldHeaderValue.IsEmpty()) {
-    rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   if (!cspHeaderValue.IsEmpty()) {
     rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
     NS_ENSURE_SUCCESS(rv, rv);
+  } else if (!cspOldHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // ----- if there's a report-only CSP header, apply it.
-  if (!cspOldROHeaderValue.IsEmpty()) {
-    rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   if (!cspROHeaderValue.IsEmpty()) {
     rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true);
     NS_ENSURE_SUCCESS(rv, rv);
+  } else if (!cspOldROHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // ----- Enforce frame-ancestor policy on any applied policies
   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
   if (docShell) {
     bool safeAncestry = false;
 
     // PermitsAncestry sends violation reports when necessary
--- a/content/base/test/csp/file_bothCSPheaders.html
+++ b/content/base/test/csp/file_bothCSPheaders.html
@@ -1,5 +1,6 @@
 <html>
 <body>
-<img src="http://example.org/nonexistent.jpg"></img>
+<img src="http://example.org/prefixed.jpg"></img>
+<img src="/unprefixed.jpg"></img>
 </body>
 </html>
--- a/content/base/test/csp/file_bothCSPheaders.html^headers^
+++ b/content/base/test/csp/file_bothCSPheaders.html^headers^
@@ -1,2 +1,2 @@
-X-Content-Security-Policy: default-src 'self' ; img-src 'self' http://example.org
+X-Content-Security-Policy: default-src 'none' ; img-src http://example.org
 Content-Security-Policy: default-src 'self'
--- a/content/base/test/csp/test_bothCSPheaders.html
+++ b/content/base/test/csp/test_bothCSPheaders.html
@@ -1,78 +1,83 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for Correctly Handling Both Pre-1.0 and 1.0 Content Security Policy Headers</title>
+  <!-- When both headers are present, we should ignore the pre-1.0 header and
+       only recognize the 1.0 spec-compliant header. -->
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 
 <iframe style="width:200px;height:200px;" id='cspframe'></iframe>
 <script class="testbody" type="text/javascript">
 
-var loadedImgURL = "http://example.org/nonexistent.jpg";
+var prefixedHeaderImgURL = "http://example.org/prefixed.jpg";
+var unprefixedHeaderImgURL = "http://mochi.test:8888/unprefixed.jpg";
+var testsRun = 0;
+var totalTests = 2;
 
-// This is used to watch the blocked data bounce off CSP and allowed data 
+// This is used to watch the blocked data bounce off CSP and allowed data
 // get sent out to the wire.
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   SpecialPowers.addObserver(this, "http-on-modify-request", false);
 }
 examiner.prototype  = {
   observe: function(subject, topic, data) {
     // subject should be an nsURI, and should be either allowed or blocked.
     if(!SpecialPowers.can_QI(subject))
       return;
 
     if (topic === "http-on-modify-request") {
-      // the load was allowed, this is a fail, the Content-Security Policy
-      // should not allow the load
       var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
-      if (asciiSpec != loadedImgURL) return;
-
-      ok(false, "the Content-Security Policy header does not allow the load, the X-Content-Security header should be ignored");
-      window.examiner.remove();
-      SimpleTest.finish();
+      if (asciiSpec == prefixedHeaderImgURL || asciiSpec == unprefixedHeaderImgURL) {
+        is(asciiSpec, unprefixedHeaderImgURL, "Load was allowed - should be allowed by unprefixed header (blocked by prefixed)");
+        testRan();
+      }
     }
 
     if (topic === "csp-on-violate-policy") {
       // the load was blocked, this is a pass, the Content-Security-Policy
       // header doesn't allow the load, but the X-Content-Security-Header does
       var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
-      if (asciiSpec != loadedImgURL) return;
-
-      ok(true, "Load was blocked - the Content-Security-Policy header doesn't allow the load, the X-Content-Security-Header does but should have been ignored");
-      window.examiner.remove();
-      SimpleTest.finish();
+      if (asciiSpec == prefixedHeaderImgURL || asciiSpec == unprefixedHeaderImgURL) {
+        is(asciiSpec, prefixedHeaderImgURL, "Load was blocked - the Content-Security-Policy header doesn't allow the load, the X-Content-Security-Header does but should have been ignored");
+        testRan();
+      }
     }
   },
 
   // must eventually call this to remove the listener,
   // or mochitests might get borked.
   remove: function() {
     SpecialPowers.removeObserver(this, "csp-on-violate-policy");
     SpecialPowers.removeObserver(this, "http-on-modify-request");
   }
 }
 
 window.examiner = new examiner();
-
 SimpleTest.waitForExplicitFinish();
 
-// save this for last so that our listeners are registered.
-// ... this loads the testbed of good and bad requests.
+function testRan() {
+  testsRun++;
+  if (testsRun == totalTests) {
+    window.examiner.remove();
+    SimpleTest.finish();
+  }
+}
+
 SpecialPowers.pushPrefEnv(
-{'set':[["security.csp.speccompliant", true]]},
-  function() {
-    // save this for last so that our listeners are registered.
-    // ... this loads the testbed of good and bad requests.
-    document.getElementById('cspframe').src = 'file_bothCSPheaders.html';
+  {'set':[["security.csp.speccompliant", true]]},
+  function loadTestRequests() {
+    var cspframe = document.getElementById('cspframe');
+    cspframe.src = 'file_bothCSPheaders.html';
   }
 );
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/forms/test_input_range_key_events.html
+++ b/content/html/content/test/forms/test_input_range_key_events.html
@@ -19,16 +19,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script type="application/javascript">
 
 /**
  * Test for Bug 843725
  * This test checks how the value of <input type=range> changes in response to
  * various key events while it is in various states.
  **/
 SimpleTest.waitForExplicitFinish();
+
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 SimpleTest.waitForFocus(function() {
   test();
   SimpleTest.finish();
 });
 
 const defaultMinimum = 0;
 const defaultMaximum = 100;
 const defaultStep = 1;
--- a/content/html/content/test/forms/test_input_range_rounding.html
+++ b/content/html/content/test/forms/test_input_range_rounding.html
@@ -21,16 +21,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /**
  * Test for Bug 853525
  * This test checks that when <input type=range> has fractional step values,
  * the values that a content author will see in their script will not have
  * ugly rounding errors.
  **/
 SimpleTest.waitForExplicitFinish();
+
+// Turn off spatial navigation because it hijacks arrow keydown events:
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 SimpleTest.waitForFocus(function() {
   test();
   SimpleTest.finish();
 });
 
 /**
  * We can _NOT_ generate these values by looping and simply incrementing a
  * variable by 0.01 and stringifying it, since we'll end up with strings like
--- a/content/html/content/test/test_bug633058.html
+++ b/content/html/content/test/test_bug633058.html
@@ -17,16 +17,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 633058 **/
 
 SimpleTest.waitForExplicitFinish();
 
+// Turn off Spatial Navigation so that the 'keypress' event fires.
+SpecialPowers.setBoolPref('snav.enabled', false);
+
 SimpleTest.waitForFocus(function() {
   var nbExpectedKeyPress = 8;
   var inputGotKeyPress = 0;
   var divGotKeyPress = 0;
 
   var input = document.getElementsByTagName('input')[0];
   var content = document.getElementById('content');
 
--- a/content/html/content/test/test_bug674558.html
+++ b/content/html/content/test/test_bug674558.html
@@ -14,16 +14,21 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 674558 **/
 SimpleTest.waitForExplicitFinish();
+
+// Turn off spatial navigation because it hijacks VK_RIGHT and VK_LEFT keydown
+// events.
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 SimpleTest.waitForFocus(function() {
   function textAreaCtor() {
     return document.createElement("textarea");
   }
   var ctors = [textAreaCtor];
   ["text", "password", "search"].forEach(function(type) {
     ctors.push(function inputCtor() {
       var input = document.createElement("input");
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -689,17 +689,19 @@ nsresult nsGeolocationService::Init()
   mProvider = new AndroidLocationProvider();
 #endif
 
 #ifdef MOZ_WIDGET_GONK
   mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
 #endif
 
 #ifdef MOZ_WIDGET_COCOA
-  mProvider = new CoreLocationLocationProvider();
+  if (Preferences::GetBool("geo.provider.use_corelocation", false)) {
+    mProvider = new CoreLocationLocationProvider();
+  }
 #endif
 
   // Override platform-specific providers with the default (network)
   // provider while testing. Our tests are currently not meant to exercise
   // the provider, and some tests rely on the network provider being used.
   // "geo.provider.testing" is always set for all plain and browser chrome
   // mochitests, and also for xpcshell tests.
   if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
--- a/dom/tests/mochitest/bugs/test_bug265203.html
+++ b/dom/tests/mochitest/bugs/test_bug265203.html
@@ -36,16 +36,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 265203 **/
 
+// Turn off spatial navigation because it hijacks VK_RIGHT and VK_LEFT keydown
+// events
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 var gTestStarted = false;
 var expectedResult = [ null, 0, null ];
 var nextTest;
 var test = 0;
 
 function testFocus() {
     var selection = window.getSelection()
     is(selection.focusNode, expectedResult[0],"test" + test + ": " + "caret node");
--- a/editor/libeditor/base/tests/test_bug795785.html
+++ b/editor/libeditor/base/tests/test_bug795785.html
@@ -18,17 +18,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
+
 SimpleTest.waitForExplicitFinish();
+
+// Turn off spatial navigation because it hijacks arrow key events and VK_RETURN
+// events.
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 SimpleTest.waitForFocus(runTests);
 
 var textarea = document.getElementById("textarea");
 var div = document.getElementById("div");
 
 function hitEventLoop(aFunc, aTimes)
 {
   if (--aTimes) {
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -4642,36 +4642,38 @@ nsCSSFrameConstructor::ConstructFrameWit
                                    bool                     aCandidateRootFrame)
 {
   nsIContent* const content = aItem.mContent;
   nsStyleContext* const styleContext = aItem.mStyleContext;
 
   // Create the outer frame:
   nsIFrame* newFrame = aConstructor(mPresShell, styleContext);
 
-  nsIFrame* geometricParent =
-    aState.GetGeometricParent(styleContext->StyleDisplay(),
-                              aParentFrame);
-
-  InitAndRestoreFrame(aState, content, geometricParent, newFrame);
+  InitAndRestoreFrame(aState, content,
+                      aCandidateRootFrame ?
+                        aState.GetGeometricParent(styleContext->StyleDisplay(),
+                                                  aParentFrame) :
+                        aParentFrame,
+                      newFrame);
 
   // Create the pseudo SC for the anonymous wrapper child as a child of the SC:
   nsRefPtr<nsStyleContext> scForAnon;
   scForAnon = mPresShell->StyleSet()->
     ResolveAnonymousBoxStyle(aInnerPseudo, styleContext);
 
   // Create the anonymous inner wrapper frame
   nsIFrame* innerFrame = aInnerConstructor(mPresShell, scForAnon);
 
   InitAndRestoreFrame(aState, content, newFrame, innerFrame);
 
   // Put the newly created frames into the right child list
   SetInitialSingleChild(newFrame, innerFrame);
 
-  aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
+  aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame,
+                  aCandidateRootFrame, aCandidateRootFrame);
 
   if (!mRootElementFrame && aCandidateRootFrame) {
     // The frame we're constructing will be the root element frame.
     // Set mRootElementFrame before processing children.
     mRootElementFrame = newFrame;
   }
 
   nsFrameItems childItems;
--- a/layout/forms/test/test_bug345267.html
+++ b/layout/forms/test/test_bug345267.html
@@ -23,16 +23,20 @@ https://bugzilla.mozilla.org/show_bug.cg
  <input id="u2" maxlength="3">
  <input id="u3" maxlength="3">
  <input id="u4" value="abcdefghijkl">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 345267 **/
+
+// Turn off Spatial Navigation to stop if from hijacking "left" keypress event.
+SpecialPowers.setBoolPref('snav.enabled', false);
+
 is($("d1").value, "abcde",
    "Displayed initial value should not be truncated by maxlength");
 is($("u1").value, "abcdef",
    "Undisplayed initial value should not be truncated by maxlength");
 
 $("d2").value = "abcdefg";
 is($("d2").value, "abcdefg",
    "Displayed set value should not be truncated by maxlength");
--- a/layout/forms/test/test_bug365410.html
+++ b/layout/forms/test/test_bug365410.html
@@ -69,16 +69,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 365410 **/
 
+// Turn off spatial nav so that it does not hijack the up and down events.
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 function pageUpDownTest(id,index) {
   var elm = document.getElementById(id);
   elm.focus();
   elm.selectedIndex = 0;
   sendKey("page_down");
   sendKey("page_down");
   sendKey("page_up");
   sendKey("page_down");
--- a/layout/forms/test/test_bug563642.html
+++ b/layout/forms/test/test_bug563642.html
@@ -32,16 +32,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 563642 **/
 
+// Turn off Spatial Navigation because it hijacks down and up key events.
+SpecialPowers.setBoolPref("snav.enabled", false);
+
 function pageUpDownTest(id,index) {
   var elm = document.getElementById(id);
   elm.focus();
   elm.selectedIndex = 0;
   sendKey("page_down");
   sendKey("page_down");
   sendKey("page_down");
   sendKey("page_up");
--- a/layout/forms/test/test_select_prevent_default.html
+++ b/layout/forms/test/test_select_prevent_default.html
@@ -6,16 +6,21 @@ https://bugzilla.mozilla.org/show_bug.cg
 <head>
 <meta charset="utf-8">
 <title>Test for Bug 291082</title>
 <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 <script type="application/javascript">
   /** Test for Bug 291082 **/
+
+
+  // Turn off Spatial Navigation because it hijacks arrow keydown events.
+  SpecialPowers.setBoolPref("snav.enabled", false);
+
   SimpleTest.waitForExplicitFinish();
 
   function preventDefault(event) {
     event.preventDefault();
   }
 
   function test() {
     document.getElementById("keydown").addEventListener("keydown", preventDefault);
new file mode 100644
--- /dev/null
+++ b/layout/svg/crashtests/919371-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <svg xmlns="http://www.w3.org/2000/svg">
+    <marker style="position: absolute;" />
+  </svg>
+</html>
--- a/layout/svg/crashtests/crashtests.list
+++ b/layout/svg/crashtests/crashtests.list
@@ -171,8 +171,9 @@ load 881031-1.svg
 load 885608-1.svg
 load 890782-1.svg
 load 890783-1.svg
 load 893510-1.svg
 load 895311-1.svg
 load 897342-1.svg
 load 898909-1.svg
 load 898951-1.svg
+load 919371-1.xhtml
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -114,37 +114,43 @@ struct cubeb_stream
   HANDLE refill_event;
   /* Each cubeb_stream has its own thread. */
   HANDLE thread;
   uint64_t clock_freq;
   /* Maximum number of frames we can be requested in a callback. */
   uint32_t buffer_frame_count;
   /* Resampler instance. If this is !NULL, resampling should happen. */
   SpeexResamplerState * resampler;
-  /* Buffer to resample from, into the upmix buffer or the final buffer. */
+  /* Buffer to resample from, into the mix buffer or the final buffer. */
   float * resampling_src_buffer;
   /* Pointer to the function used to refill the buffer, depending
    * on the respective samplerate of the stream and the mix. */
   refill_function2 refill_function;
   /* Leftover frames handling, only used when resampling. */
   uint32_t leftover_frame_count;
   uint32_t leftover_frame_size;
   float * leftover_frames_buffer;
-  /* upmix buffer of size |buffer_frame_count * bytes_per_frame / 2|. */
-  float * upmix_buffer;
-  /* Number of bytes per frame. Prefer to use frames_to_bytes_before_upmix. */
+  /* Buffer used to downmix or upmix to the number of channels the mixer has.
+   * its size is |buffer_frame_count * bytes_per_frame * mixer_channels|. */
+  float * mix_buffer;
+  /* Number of bytes per frame. Prefer to use frames_to_bytes_before_mix. */
   uint8_t bytes_per_frame;
   /* True if the stream is draining. */
   bool draining;
 };
 
 namespace {
 bool should_upmix(cubeb_stream * stream)
 {
-  return stream->upmix_buffer;
+  return stream->mix_params.channels > stream->stream_params.channels;
+}
+
+bool should_downmix(cubeb_stream * stream)
+{
+  return stream->mix_params.channels < stream->stream_params.channels;
 }
 
 /* Upmix function, copies a mono channel in two interleaved
  * stereo channel. |out| has to be twice as long as |in| */
 template<typename T>
 void
 mono_to_stereo(T * in, long insamples, T * out)
 {
@@ -174,20 +180,33 @@ upmix(T * in, long inframes, T * out, in
     }
     for (int j = in_channels; j < out_channels; j++) {
       out[out_index + j] = 0.0;
     }
     out_index += out_channels;
   }
 }
 
+template<typename T>
+void
+downmix_to_stereo(T * in, long inframes, T * out, int32_t in_channels)
+{
+  /* We could use a downmix matrix here, applying mixing weight based on the
+   * channel, but directsound and winmm simply drop the channels that cannot be
+   * rendered by the hardware, so we do the same for consistency. */
+  for (int32_t i = 0; i < inframes; i++) {
+    out[i * 2] = in[i * in_channels];
+    out[i * 2 + 1] = in[i * in_channels + 1];
+  }
+}
+
 /* This returns the size of a frame in the stream,
  * before the eventual upmix occurs. */
 static size_t
-frame_to_bytes_before_upmix(cubeb_stream * stm, size_t frames)
+frame_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
 {
   size_t stream_frame_size = stm->stream_params.channels * sizeof(float);
   return stream_frame_size * frames;
 }
 
 void
 refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
 {
@@ -197,91 +216,96 @@ refill_with_resampling(cubeb_stream * st
   float rate =
     static_cast<float>(stm->stream_params.rate) / stm->mix_params.rate;
 
   long before_resampling = frame_count_at_rate(frames_needed, rate);
 
   long frame_requested = before_resampling - stm->leftover_frame_count;
 
   size_t leftover_bytes =
-    frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
+    frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
 
   /* Copy the previous leftover frames to the front of the buffer. */
   memcpy(stm->resampling_src_buffer, stm->leftover_frames_buffer, leftover_bytes);
   uint8_t * buffer_start = reinterpret_cast<uint8_t *>(
                                   stm->resampling_src_buffer) + leftover_bytes;
 
   long got = stm->data_callback(stm, stm->user_ptr, buffer_start, frame_requested);
 
   if (got != frame_requested) {
     stm->draining = true;
   }
 
   uint32_t in_frames = before_resampling;
   uint32_t out_frames = frames_needed;
 
-  /* if we need to upmix after resampling, resample into
-   * the upmix buffer to avoid a copy */
+  /* If we need to upmix after resampling, resample into the mix buffer to
+   * avoid a copy. */
   float * resample_dest;
-  if (should_upmix(stm)) {
-    resample_dest = stm->upmix_buffer;
+  if (should_upmix(stm) || should_downmix(stm)) {
+    resample_dest = stm->mix_buffer;
   } else {
     resample_dest = data;
   }
 
   speex_resampler_process_interleaved_float(stm->resampler,
                                             stm->resampling_src_buffer,
                                             &in_frames,
                                             resample_dest,
                                             &out_frames);
 
   /* Copy the leftover frames to buffer for the next time. */
   stm->leftover_frame_count = before_resampling - in_frames;
   size_t unresampled_bytes =
-    frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
+    frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
 
   uint8_t * leftover_frames_start =
     reinterpret_cast<uint8_t *>(stm->resampling_src_buffer);
-  leftover_frames_start += frame_to_bytes_before_upmix(stm, in_frames);
+  leftover_frames_start += frame_to_bytes_before_mix(stm, in_frames);
 
   assert(stm->leftover_frame_count <= stm->leftover_frame_size);
   memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes);
 
   /* If this is not true, there will be glitches.
    * It is alright to have produced less frames if we are draining, though. */
   assert(out_frames == frames_needed || stm->draining);
 
   if (should_upmix(stm)) {
     upmix(resample_dest, out_frames, data,
           stm->stream_params.channels, stm->mix_params.channels);
+  } else if (should_downmix(stm)) {
+    downmix_to_stereo(resample_dest, out_frames, data,
+                      stm->stream_params.channels);
   }
 }
 
 void
 refill(cubeb_stream * stm, float * data, long frames_needed)
 {
-  /* If we need to upmix after resampling, get the data into
-   * the upmix buffer to avoid a copy. */
+  /* If we need to upmix/downmix, get the data into the mix buffer to avoid a
+   * copy, then do the processing process. */
   float * dest;
-  if (should_upmix(stm)) {
-    dest = stm->upmix_buffer;
+  if (should_upmix(stm) || should_downmix(stm)) {
+    dest = stm->mix_buffer;
   } else {
     dest = data;
   }
 
   long got = stm->data_callback(stm, stm->user_ptr, dest, frames_needed);
 
   if (got != frames_needed) {
     LOG("draining.");
     stm->draining = true;
   }
 
   if (should_upmix(stm)) {
     upmix(dest, got, data,
           stm->stream_params.channels, stm->mix_params.channels);
+  } else {
+    downmix_to_stereo(dest, got, data, stm->stream_params.channels);
   }
 }
 
 static unsigned int __stdcall
 wasapi_stream_render_loop(LPVOID stream)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
 
@@ -509,23 +533,24 @@ wasapi_get_max_channel_count(cubeb * ctx
 
 void wasapi_stream_destroy(cubeb_stream * stm);
 
 /* Based on the mix format and the stream format, try to find a way to play what
  * the user requested. */
 static void
 handle_channel_layout(cubeb_stream * stm,  WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
 {
-  /* Common case: the hardware supports stereo, and the stream is mono or
-   * stereo. Easy. */
-  if ((*mix_format)->nChannels == 2 &&
-      stream_params->channels <= 2) {
+  /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
+   * handled in the callback. */
+  if ((*mix_format)->nChannels == 2) {
     return;
   }
 
+  /* Otherwise, the hardware supports more than two channels. */
+
   /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
    * so the reinterpret_cast below should be safe. In practice, this is not
    * true, and we just want to bail out and let the rest of the code find a good
    * conversion path instead of trying to make WASAPI do it by itself.
    * [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
   if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
     return;
   }
@@ -554,17 +579,17 @@ handle_channel_layout(cubeb_stream * stm
   /* Check if wasapi will accept our channel layout request. */
   WAVEFORMATEX * closest;
   HRESULT hr = stm->client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
                                               *mix_format,
                                               &closest);
 
   if (hr == S_FALSE) {
     /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
-     * eventual upmix ourselve */
+     * eventual upmix/downmix ourselve */
     LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
     CoTaskMemFree(*mix_format);
     *mix_format = closest;
   } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
     /* Not supported, no suggestion, there is a bug somewhere. */
     assert(false && "Format not supported, and no suggestion from WASAPI.");
   } else if (hr == S_OK) {
     LOG("Requested format accepted by WASAPI.");
@@ -591,21 +616,16 @@ wasapi_stream_init(cubeb * context, cube
   }
 
   /* 30ms in shared mode is the minimum we can get when using WASAPI */
   if (latency < 30) {
     LOG("Latency too low: got %u (30ms minimum)", latency);
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
-  /* we don't support more that two channels for now. */
-  if (stream_params.channels > 2) {
-    return CUBEB_ERROR_INVALID_FORMAT;
-  }
-
   cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
 
   assert(stm);
 
   stm->context = context;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
@@ -675,17 +695,17 @@ wasapi_stream_init(cubeb * context, cube
       wasapi_stream_destroy(stm);
       return CUBEB_ERROR;
     }
 
     /* Get a little buffer so we can store the leftover frames,
      * that is, the samples not consumed by the resampler that we will end up
      * using next time the render callback is called. */
     stm->leftover_frame_size = static_cast<uint32_t>(ceilf(1 / resampling_rate * 2) + 1);
-    stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, stm->leftover_frame_size));
+    stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, stm->leftover_frame_size));
 
     stm->refill_function = &refill_with_resampling;
   } else {
     stm->refill_function = &refill;
   }
 
   hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED,
                                AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
@@ -707,27 +727,27 @@ wasapi_stream_init(cubeb * context, cube
   if (FAILED(hr)) {
     LOG("Could not get the buffer size from the client.");
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   assert(stm->mix_params.channels >= 2);
 
-  if (stm->mix_params.channels != stm->stream_params.channels) {
-    stm->upmix_buffer = (float *) malloc(frame_to_bytes_before_upmix(stm, stm->buffer_frame_count));
+  if (should_upmix(stm) || should_downmix(stm)) {
+    stm->mix_buffer = (float *) malloc(frame_to_bytes_before_mix(stm, stm->buffer_frame_count));
   }
 
   /* If we are going to resample, we will end up needing a buffer
    * to resample from, because speex's resampler does not do
    * in-place processing. Of course we need to take the resampling
    * factor and the channel layout into account. */
   if (stm->resampler) {
     size_t frames_needed = static_cast<size_t>(frame_count_at_rate(stm->buffer_frame_count, resampling_rate));
-    stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, frames_needed));
+    stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, frames_needed));
   }
 
   hr = stm->client->SetEventHandle(stm->refill_event);
   if (FAILED(hr)) {
     LOG("Could set the event handle for the client.");
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
@@ -778,17 +798,17 @@ void wasapi_stream_destroy(cubeb_stream 
   SafeRelease(stm->audio_clock);
 
   if (stm->resampler) {
     speex_resampler_destroy(stm->resampler);
   }
 
   free(stm->leftover_frames_buffer);
   free(stm->resampling_src_buffer);
-  free(stm->upmix_buffer);
+  free(stm->mix_buffer);
   free(stm);
   CoUninitialize();
 }
 
 int wasapi_stream_start(cubeb_stream * stm)
 {
   HRESULT hr;
 
--- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h
@@ -52,22 +52,38 @@ struct VideoCodecConfig
 {
   /*
    * The data-types for these properties mimic the
    * corresponding webrtc::VideoCodec data-types.
    */
   int mType;
   std::string mName;
   uint32_t mRtcpFbTypes;
+  unsigned int mMaxFrameSize;
+  unsigned int mMaxFrameRate;
 
   VideoCodecConfig(int type,
                    std::string name,
                    int rtcpFbTypes): mType(type),
                                      mName(name),
-                                     mRtcpFbTypes(rtcpFbTypes)
+                                     mRtcpFbTypes(rtcpFbTypes),
+                                     mMaxFrameSize(0),
+                                     mMaxFrameRate(0)
+  {
+  }
+
+  VideoCodecConfig(int type,
+                   std::string name,
+                   int rtcpFbTypes,
+                   unsigned int max_fs,
+                   unsigned int max_fr): mType(type),
+                                         mName(name),
+                                         mRtcpFbTypes(rtcpFbTypes),
+                                         mMaxFrameSize(max_fs),
+                                         mMaxFrameRate(max_fr)
   {
   }
 
 
   bool RtcpFbIsSet(sdp_rtcp_fb_nack_type_e type) const
   {
     return mRtcpFbTypes & sdp_rtcp_fb_nack_to_bitmap(type);
   }
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -216,16 +216,28 @@ public:
    *        reception sub-system on the engine
    *
    */
   virtual MediaConduitErrorCode ConfigureRecvMediaCodecs(
                                 const std::vector<VideoCodecConfig* >& recvCodecConfigList) = 0;
 
 
   /**
+   * These methods allow unit tests to double-check that the
+   * max-fs and max-fr related settings are as expected.
+   */
+  virtual unsigned short SendingWidth() = 0;
+
+  virtual unsigned short SendingHeight() = 0;
+
+  virtual unsigned int SendingMaxFs() = 0;
+
+  virtual unsigned int SendingMaxFr() = 0;
+
+  /**
     * These methods allow unit tests to double-check that the
     * rtcp-fb settings are as expected.
     */
     FrameRequestType FrameRequestMethod() const {
       return mFrameRequestMethod;
     }
 
     bool UsingNackBasic() const {
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -11,16 +11,19 @@
 #include "VideoConduit.h"
 #include "AudioConduit.h"
 #include "webrtc/video_engine/include/vie_errors.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidJNIWrapper.h"
 #endif
 
+#include <algorithm>
+#include <math.h>
+
 namespace mozilla {
 
 static const char* logTag ="WebrtcVideoSessionConduit";
 
 const unsigned int WebrtcVideoConduit::CODEC_PLNAME_SIZE = 32;
 
 //Factory Implementation
 mozilla::RefPtr<VideoSessionConduit> VideoSessionConduit::Create()
@@ -611,16 +614,81 @@ WebrtcVideoConduit::ConfigureRecvMediaCo
 // XXX we need to figure out how to feed back changes in preferred capture
 // resolution to the getUserMedia source
 bool
 WebrtcVideoConduit::SelectSendResolution(unsigned short width,
                                          unsigned short height)
 {
   // XXX This will do bandwidth-resolution adaptation as well - bug 877954
 
+  // Limit resolution to max-fs while keeping same aspect ratio as the
+  // incoming image.
+  if (mCurSendCodecConfig && mCurSendCodecConfig->mMaxFrameSize)
+  {
+    unsigned int cur_fs, max_width, max_height, mb_width, mb_height, mb_max;
+
+    mb_width = (width + 15) >> 4;
+    mb_height = (height + 15) >> 4;
+
+    cur_fs = mb_width * mb_height;
+
+    // Limit resolution to max_fs, but don't scale up.
+    if (cur_fs > mCurSendCodecConfig->mMaxFrameSize)
+    {
+      double scale_ratio;
+
+      scale_ratio = sqrt((double) mCurSendCodecConfig->mMaxFrameSize /
+                         (double) cur_fs);
+
+      mb_width = mb_width * scale_ratio;
+      mb_height = mb_height * scale_ratio;
+
+      // Adjust mb_width and mb_height if they were truncated to zero.
+      if (mb_width == 0) {
+        mb_width = 1;
+        mb_height = std::min(mb_height, mCurSendCodecConfig->mMaxFrameSize);
+      }
+      if (mb_height == 0) {
+        mb_height = 1;
+        mb_width = std::min(mb_width, mCurSendCodecConfig->mMaxFrameSize);
+      }
+    }
+
+    // Limit width/height seperately to limit effect of extreme aspect ratios.
+    mb_max = (unsigned) sqrt(8 * (double) mCurSendCodecConfig->mMaxFrameSize);
+
+    max_width = 16 * std::min(mb_width, mb_max);
+    max_height = 16 * std::min(mb_height, mb_max);
+
+    if (width * max_height > max_width * height)
+    {
+      if (width > max_width)
+      {
+        // Due to the value is truncated to integer here and forced to even
+        // value later, adding 1 to improve accuracy.
+        height = max_width * height / width + 1;
+        width = max_width;
+      }
+    }
+    else
+    {
+      if (height > max_height)
+      {
+        // Due to the value is truncated to integer here and forced to even
+        // value later, adding 1 to improve accuracy.
+        width = max_height * width / height + 1;
+        height = max_height;
+      }
+    }
+
+    // Favor even multiples of pixels for width and height.
+    width = std::max(width & ~1, 2);
+    height = std::max(height & ~1, 2);
+  }
+
   // Adapt to getUserMedia resolution changes
   // check if we need to reconfigure the sending resolution
   if (mSendingWidth != width || mSendingHeight != height)
   {
     // This will avoid us continually retrying this operation if it fails.
     // If the resolution changes, we'll try again.  In the meantime, we'll
     // keep using the old size in the encoder.
     mSendingWidth = width;
@@ -845,16 +913,20 @@ WebrtcVideoConduit::DeliverFrame(unsigne
  */
 
 void
 WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo,
                                               webrtc::VideoCodec& cinst)
 {
   cinst.plType  = codecInfo->mType;
   // leave width/height alone; they'll be overridden on the first frame
+  if (codecInfo->mMaxFrameRate > 0)
+  {
+    cinst.maxFramerate = codecInfo->mMaxFrameRate;
+  }
   cinst.minBitrate = 200;
   cinst.startBitrate = 300;
   cinst.maxBitrate = 2000;
 }
 
 bool
 WebrtcVideoConduit::CopyCodecToDB(const VideoCodecConfig* codecInfo)
 {
@@ -887,18 +959,20 @@ bool
 WebrtcVideoConduit::CheckCodecsForMatch(const VideoCodecConfig* curCodecConfig,
                                         const VideoCodecConfig* codecInfo) const
 {
   if(!curCodecConfig)
   {
     return false;
   }
 
-  if(curCodecConfig->mType   == codecInfo->mType &&
-     curCodecConfig->mName.compare(codecInfo->mName) == 0)
+  if(curCodecConfig->mType  == codecInfo->mType &&
+     curCodecConfig->mName.compare(codecInfo->mName) == 0 &&
+     curCodecConfig->mMaxFrameSize == codecInfo->mMaxFrameSize &&
+     curCodecConfig->mMaxFrameRate == codecInfo->mMaxFrameRate)
   {
     return true;
   }
 
   return false;
 }
 
 /**
@@ -942,12 +1016,14 @@ WebrtcVideoConduit::ValidateCodecConfig(
 
 void
 WebrtcVideoConduit::DumpCodecDB() const
 {
   for(std::vector<VideoCodecConfig*>::size_type i=0;i<mRecvCodecList.size();i++)
   {
     CSFLogDebug(logTag,"Payload Name: %s", mRecvCodecList[i]->mName.c_str());
     CSFLogDebug(logTag,"Payload Type: %d", mRecvCodecList[i]->mType);
+    CSFLogDebug(logTag,"Payload Max Frame Size: %d", mRecvCodecList[i]->mMaxFrameSize);
+    CSFLogDebug(logTag,"Payload Max Frame Rate: %d", mRecvCodecList[i]->mMaxFrameRate);
   }
 }
 
 }// end namespace
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -147,16 +147,37 @@ public:
   /**
    * Webrtc External Renderer Implementation APIs.
    * Raw I420 Frames are delivred to the VideoConduit by the VideoEngine
    */
   virtual int FrameSizeChange(unsigned int, unsigned int, unsigned int);
 
   virtual int DeliverFrame(unsigned char*,int, uint32_t , int64_t);
 
+  unsigned short SendingWidth() {
+    return mSendingWidth;
+  }
+
+  unsigned short SendingHeight() {
+    return mSendingHeight;
+  }
+
+  unsigned int SendingMaxFs() {
+    if(mCurSendCodecConfig) {
+      return mCurSendCodecConfig->mMaxFrameSize;
+    }
+    return 0;
+  }
+
+  unsigned int SendingMaxFr() {
+    if(mCurSendCodecConfig) {
+      return mCurSendCodecConfig->mMaxFrameRate;
+    }
+    return 0;
+  }
 
   WebrtcVideoConduit():
                       mVideoEngine(nullptr),
                       mTransport(nullptr),
                       mRenderer(nullptr),
                       mPtrViEBase(nullptr),
                       mPtrViECapture(nullptr),
                       mPtrViECodec(nullptr),
--- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
+++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
@@ -19,16 +19,20 @@
 #include "transportflow.h"
 #include "transportlayer.h"
 #include "transportlayerdtls.h"
 #include "transportlayerice.h"
 #include "runnable_utils.h"
 #include "cpr_stdlib.h"
 #include "cpr_string.h"
 #include "mozilla/SyncRunnable.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <ssl.h>
 #include <sslproto.h>
 #include <algorithm>
 
 extern "C" {
@@ -65,16 +69,17 @@ typedef enum {
 
 using namespace CSF;
 
 VcmSIPCCBinding * VcmSIPCCBinding::gSelf = NULL;
 int VcmSIPCCBinding::gAudioCodecMask = 0;
 int VcmSIPCCBinding::gVideoCodecMask = 0;
 nsIThread *VcmSIPCCBinding::gMainThread = NULL;
 nsIEventTarget *VcmSIPCCBinding::gSTSThread = NULL;
+nsCOMPtr<nsIPrefBranch> VcmSIPCCBinding::gBranch = NULL;
 
 static mozilla::RefPtr<TransportFlow> vcmCreateTransportFlow(
     sipcc::PeerConnectionImpl *pc,
     int level,
     bool rtcp,
     sdp_setup_type_e setup_type,
     const char *fingerprint_alg,
     const char *fingerprint);
@@ -97,16 +102,22 @@ static mozilla::RefPtr<TransportFlow> vc
     }         \
   } while(0)
 
 VcmSIPCCBinding::VcmSIPCCBinding ()
   : streamObserver(NULL)
 {
     delete gSelf;//delete is NULL safe, so I don't need to check if it's NULL
     gSelf = this;
+  nsresult rv;
+
+  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
+  if (NS_SUCCEEDED(rv)) {
+    gBranch = do_QueryInterface(prefs);
+  }
 }
 
 class VcmIceOpaque : public NrIceOpaque {
  public:
   VcmIceOpaque(cc_streamid_t stream_id,
                cc_call_handle_t call_handle,
                uint16_t level) :
       stream_id_(stream_id),
@@ -128,16 +139,18 @@ VcmSIPCCBinding::~VcmSIPCCBinding ()
     // In case we're torn down while STS is still running,
     // we try to dispatch to STS to disconnect all of the
     // ICE signals. If STS is no longer running, this will
     // harmlessly fail.
     SyncRunnable::DispatchToThread(
       gSTSThread,
       WrapRunnable(this, &VcmSIPCCBinding::disconnect_all),
       true);
+
+  gBranch = NULL;
 }
 
 void VcmSIPCCBinding::CandidateReady(NrIceMediaStream* stream,
                                      const std::string& candidate)
 {
     // This is called on the STS thread
     NrIceOpaque *opaque = stream->opaque();
     MOZ_ASSERT(opaque);
@@ -230,16 +243,21 @@ nsIEventTarget* VcmSIPCCBinding::getSTST
 
 void VcmSIPCCBinding::connectCandidateSignal(
     NrIceMediaStream *stream)
 {
   stream->SignalCandidate.connect(gSelf,
                                   &VcmSIPCCBinding::CandidateReady);
 }
 
+nsCOMPtr<nsIPrefBranch> VcmSIPCCBinding::getPrefBranch()
+{
+  return gBranch;
+}
+
 /* static */
 AudioTermination * VcmSIPCCBinding::getAudioTermination()
 {
     // commenting as part of media provider removal
     return NULL;
 }
 
 /* static */
@@ -2244,17 +2262,19 @@ static int vcmTxStartICE_m(cc_mcapid_t m
     // Now we have all the pieces, create the pipeline
     stream->StorePipeline(pc_track_id, pipeline);
 
   } else if (CC_IS_VIDEO(mcap_id)) {
     mozilla::VideoCodecConfig *config_raw;
     config_raw = new mozilla::VideoCodecConfig(
       payload->remote_rtp_pt,
       ccsdpCodecName(payload->codec_type),
-      payload->video.rtcp_fb_types);
+      payload->video.rtcp_fb_types,
+      payload->video.max_fs,
+      payload->video.max_fr);
 
     // Take possession of this pointer
     mozilla::ScopedDeletePtr<mozilla::VideoCodecConfig> config(config_raw);
 
     // Instantiate an appropriate conduit
     mozilla::RefPtr<mozilla::VideoSessionConduit> conduit =
       mozilla::VideoSessionConduit::Create();
 
@@ -3048,8 +3068,52 @@ int vcmDisableRtcpComponent(const char *
   mozilla::SyncRunnable::DispatchToThread(VcmSIPCCBinding::getMainThread(),
       WrapRunnableNMRet(&vcmDisableRtcpComponent_m,
                         peerconnection,
                         level,
                         &ret));
   return ret;
 }
 
+static short vcmGetVideoMaxFs_m(uint16_t codec,
+                                int32_t *max_fs) {
+  nsCOMPtr<nsIPrefBranch> branch = VcmSIPCCBinding::getPrefBranch();
+  if (branch && NS_SUCCEEDED(branch->GetIntPref("media.navigator.video.max_fs",
+                                                max_fs))) {
+    return 0;
+  }
+  return VCM_ERROR;
+}
+
+short vcmGetVideoMaxFs(uint16_t codec,
+                       int32_t *max_fs) {
+  short ret;
+
+  mozilla::SyncRunnable::DispatchToThread(VcmSIPCCBinding::getMainThread(),
+      WrapRunnableNMRet(&vcmGetVideoMaxFs_m,
+                        codec,
+                        max_fs,
+                        &ret));
+  return ret;
+}
+
+static short vcmGetVideoMaxFr_m(uint16_t codec,
+                                int32_t *max_fr) {
+  nsCOMPtr<nsIPrefBranch> branch = VcmSIPCCBinding::getPrefBranch();
+  if (branch && NS_SUCCEEDED(branch->GetIntPref("media.navigator.video.max_fr",
+                                                max_fr))) {
+    return 0;
+  }
+  return VCM_ERROR;
+}
+
+short vcmGetVideoMaxFr(uint16_t codec,
+                       int32_t *max_fr) {
+  short ret;
+
+  mozilla::SyncRunnable::DispatchToThread(VcmSIPCCBinding::getMainThread(),
+      WrapRunnableNMRet(&vcmGetVideoMaxFr_m,
+                        codec,
+                        max_fr,
+                        &ret));
+  return ret;
+}
+
--- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.h
+++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.h
@@ -9,16 +9,17 @@ extern "C"
 {
 #include "ccapi_types.h"
 }
 
 #include "sigslot.h"
 
 class nsIThread;
 class nsIEventTarget;
+class nsIPrefBranch;
 
 namespace mozilla {
     class NrIceMediaStream;
 };
 
 namespace CSF
 {
     class AudioTermination;
@@ -64,25 +65,28 @@ namespace CSF
 	static void setMainThread(nsIThread *thread);
 	static nsIThread *getMainThread();
 	static nsIEventTarget *getSTSThread();
 
 	static void setSTSThread(nsIEventTarget *thread);
 
 	static void connectCandidateSignal(mozilla::NrIceMediaStream* stream);
 
+        static nsCOMPtr<nsIPrefBranch> getPrefBranch();
+
     private:
 	void CandidateReady(mozilla::NrIceMediaStream* stream,
 			    const std::string& candidate);
 
         static VcmSIPCCBinding * gSelf;
         StreamObserver* streamObserver;
         MediaProviderObserver *mediaProviderObserver;
         static int gAudioCodecMask;
         static int gVideoCodecMask;
 	static nsIThread *gMainThread;
 	static nsIEventTarget *gSTSThread;
+        static nsCOMPtr<nsIPrefBranch> gBranch;
     };
 }
 
 #endif
 
 
--- a/media/webrtc/signaling/src/sipcc/core/common/prot_configmgr.c
+++ b/media/webrtc/signaling/src/sipcc/core/common/prot_configmgr.c
@@ -566,16 +566,38 @@ sip_config_local_supported_codecs_get (r
                 count++;
             }
         }
         codec++;
     }
     return count;
 }
 
+uint32_t
+config_get_video_max_fs(const rtp_ptype codec)
+{
+  uint32_t max_fs;
+
+  if(vcmGetVideoMaxFs(codec, (int32_t *) &max_fs) == 0) {
+    return max_fs;
+  }
+  return 0;
+}
+
+uint32_t
+config_get_video_max_fr(const rtp_ptype codec)
+{
+  uint32_t max_fr;
+
+  if(vcmGetVideoMaxFr(codec, (int32_t *) &max_fr) == 0) {
+    return max_fr;
+  }
+  return 0;
+}
+
 /*
  * sip_config_local_supported_codecs_get()
  *
  * Get the locally supported codec list.
  */
 uint16_t
 sip_config_video_supported_codecs_get (rtp_ptype aSupportedCodecs[],
                           uint16_t supportedCodecsLen, boolean isOffer)
--- a/media/webrtc/signaling/src/sipcc/core/common/prot_configmgr.h
+++ b/media/webrtc/signaling/src/sipcc/core/common/prot_configmgr.h
@@ -287,10 +287,12 @@ line_t sip_config_get_line_from_button(l
 boolean sip_config_check_line(line_t line);
 line_t sip_config_local_line_get(void);
 void sip_config_get_display_name(line_t line, char *buffer, int buffer_len);
 line_t sip_config_get_line_by_called_number(line_t start_line, const char *called_number);
 int sip_minimum_config_check(void);
 void config_set_codec_table(int codec_mask);
 int sip_config_get_keepalive_expires();
 rtp_ptype sip_config_preferred_codec(void);
+uint32_t config_get_video_max_fs(const rtp_ptype codec);
+uint32_t config_get_video_max_fr(const rtp_ptype codec);
 
 #endif /* PROT_CONFIGMGR_H_ */
--- a/media/webrtc/signaling/src/sipcc/core/gsm/gsm_sdp.c
+++ b/media/webrtc/signaling/src/sipcc/core/gsm/gsm_sdp.c
@@ -1141,16 +1141,18 @@ gsmsdp_set_2543_hold_sdp (fsmdef_dcb_t *
  *
  */
 static void
 gsmsdp_set_video_media_attributes (uint32_t media_type, void *cc_sdp_p, uint16_t level,
                              uint16_t payload_number)
 {
     uint16_t a_inst;
     void *sdp_p = ((cc_sdp_t*)cc_sdp_p)->src_sdp;
+    int max_fs = 0;
+    int max_fr = 0;
 
     switch (media_type) {
         case RTP_H263:
         case RTP_H264_P0:
         case RTP_H264_P1:
         case RTP_VP8:
         /*
          * add a=rtpmap line
@@ -1177,16 +1179,41 @@ gsmsdp_set_video_media_attributes (uint3
             (void) sdp_attr_set_rtpmap_clockrate(sdp_p, level, 0, a_inst,
                                              RTPMAP_VIDEO_CLOCKRATE);
             break;
         case RTP_VP8:
             (void) sdp_attr_set_rtpmap_encname(sdp_p, level, 0, a_inst,
                                                SIPSDP_ATTR_ENCNAME_VP8);
             (void) sdp_attr_set_rtpmap_clockrate(sdp_p, level, 0, a_inst,
                                              RTPMAP_VIDEO_CLOCKRATE);
+
+            max_fs = config_get_video_max_fs((rtp_ptype) media_type);
+            max_fr = config_get_video_max_fr((rtp_ptype) media_type);
+
+            if (max_fs || max_fr) {
+                if (sdp_add_new_attr(sdp_p, level, 0, SDP_ATTR_FMTP, &a_inst)
+                    != SDP_SUCCESS) {
+                    GSM_ERR_MSG("Failed to add attribute");
+                    return;
+                }
+
+                (void) sdp_attr_set_fmtp_payload_type(sdp_p, level, 0, a_inst,
+                                                      payload_number);
+
+                if (max_fs) {
+                    (void) sdp_attr_set_fmtp_max_fs(sdp_p, level, 0, a_inst,
+                                                    max_fs);
+                }
+
+                if (max_fr) {
+                    (void) sdp_attr_set_fmtp_max_fr(sdp_p, level, 0, a_inst,
+                                                    max_fr);
+                }
+            }
+
             break;
         }
     GSM_DEBUG("gsmsdp_set_video_media_attributes- populate attribs %d", payload_number );
 
         vcmPopulateAttribs(cc_sdp_p, level, media_type, payload_number, FALSE);
 
         break;
 
@@ -3411,33 +3438,29 @@ gsmsdp_negotiate_codec (fsmdef_dcb_t *dc
                             != NULL ) {
                         media->previous_sdp.profile_level =
                             media->profile_level;
                         sscanf(attr_label,"%x", &media->profile_level);
                     }
 
                     /* This should ultimately use RFC 6236 a=imageattr
                        if present */
-                    switch (codec) {
-                        case RTP_VP8:
-                            payload_info->video.width = 640;
-                            payload_info->video.height = 480;
-                        break;
-                        case RTP_I420:
-                            payload_info->video.width = 176;
-                            payload_info->video.height = 144;
-                        break;
-                        default:
-                            GSM_DEBUG(DEB_L_C_F_PREFIX"codec=%d not setting "
-                                "codec parameters (not implemented)\n",
-                                DEB_L_C_F_PREFIX_ARGS(GSM, dcb_p->line,
-                                dcb_p->call_id, fname), codec);
-                            payload_info->video.width = -1;
-                            payload_info->video.height = -1;
-                    }
+
+                    payload_info->video.width = 0;
+                    payload_info->video.height = 0;
+
+                    /* Set maximum frame size */
+                    payload_info->video.max_fs = 0;
+                    sdp_attr_get_fmtp_max_fs(sdp_p->dest_sdp, level, 0, 1,
+                                             &payload_info->video.max_fs);
+
+                    /* Set maximum frame rate */
+                    payload_info->video.max_fr = 0;
+                    sdp_attr_get_fmtp_max_fr(sdp_p->dest_sdp, level, 0, 1,
+                                             &payload_info->video.max_fr);
                 } /* end video */
 
                 GSM_DEBUG(DEB_L_C_F_PREFIX"codec= %d",
                       DEB_L_C_F_PREFIX_ARGS(GSM, dcb_p->line,
                                             dcb_p->call_id, fname), codec);
 
 
                 found_codec = TRUE;
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp.h
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp.h
@@ -400,16 +400,17 @@ typedef enum {
     SDP_MODE,
     SDP_LEVEL_ASYMMETRY_ALLOWED,
     SDP_MAX_AVERAGE_BIT_RATE,
     SDP_USED_TX,
     SDP_STEREO,
     SDP_USE_IN_BAND_FEC,
     SDP_MAX_CODED_AUDIO_BW,
     SDP_CBR,
+    SDP_MAX_FR,
     SDP_MAX_FMTP_PARAM,
     SDP_FMTP_PARAM_UNKNOWN
 } sdp_fmtp_codec_param_e;
 
 /* Fmtp attribute parameters values for
    fmtp attribute parameters which convey codec
    information */
 
@@ -675,16 +676,17 @@ typedef struct sdp_fmtp {
     u16                       level_asymmetry_allowed;
     u16                       interleaving_depth;
     u32                       deint_buf_req;
     u32                       max_don_diff;
     u32                       init_buf_time;
 
     u32                       max_mbps;
     u32                       max_fs;
+    u32                       max_fr;
     u32                       max_cpb;
     u32                       max_dpb;
     u32                       max_br;
     tinybool                  redundant_pic_cap;
     u32                       deint_buf_cap;
     u32                       max_rcmd_nalu_size;
     u16                       parameter_add;
 
@@ -1513,16 +1515,22 @@ extern sdp_result_e sdp_attr_set_fmtp_ma
 						   u32 max_mbps);
 
 extern sdp_result_e sdp_attr_set_fmtp_max_fs (void *sdp_ptr,
 					      u16 level,
 				              u8 cap_num,
 				              u16 inst_num,
 					      u32 max_fs);
 
+extern sdp_result_e sdp_attr_set_fmtp_max_fr (void *sdp_ptr,
+                                              u16 level,
+                                              u8 cap_num,
+                                              u16 inst_num,
+                                              u32 max_fr);
+
 extern sdp_result_e sdp_attr_set_fmtp_max_cpb (void *sdp_ptr,
 					      u16 level,
 				              u8 cap_num,
 				              u16 inst_num,
 					      u32 max_cpb);
 
 extern sdp_result_e sdp_attr_set_fmtp_max_dpb (void *sdp_ptr,
 					      u16 level,
@@ -1689,16 +1697,18 @@ extern sdp_result_e sdp_attr_get_fmtp_ma
 						    u16 level, u8 cap_num,
 						    u16 inst_num, u32 *val);
 
 
 extern sdp_result_e sdp_attr_get_fmtp_max_mbps (void *sdp_ptr, u16 level,
                                          u8 cap_num, u16 inst_num, u32 *val);
 extern sdp_result_e sdp_attr_get_fmtp_max_fs (void *sdp_ptr, u16 level,
                                        u8 cap_num, u16 inst_num, u32 *val);
+extern sdp_result_e sdp_attr_get_fmtp_max_fr (void *sdp_ptr, u16 level,
+                                       u8 cap_num, u16 inst_num, u32 *val);
 extern sdp_result_e sdp_attr_get_fmtp_max_cpb (void *sdp_ptr, u16 level,
                                         u8 cap_num, u16 inst_num, u32 *val);
 extern sdp_result_e sdp_attr_get_fmtp_max_dpb (void *sdp_ptr, u16 level,
                                         u8 cap_num, u16 inst_num, u32 *val);
 extern sdp_result_e sdp_attr_get_fmtp_max_br (void *sdp_ptr, u16 level,
                                        u8 cap_num, u16 inst_num, u32 *val);
 extern tinybool sdp_attr_fmtp_is_redundant_pic_cap (void *sdp_ptr, u16 level,
                                                     u8 cap_num,
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c
@@ -1217,29 +1217,29 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
         fmtp_p->max_mbps = (u32) strtoul_result;
 	    codec_info_found = TRUE;
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[25].name,
                                sdp_fmtp_codec_param[25].strlen) == 0) {
 	    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
 	    if (result1 != SDP_SUCCESS) {
 	        fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
 	        if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_fs");
+                    sdp_attr_fmtp_no_value(sdp_p, "max-fs");
 		    SDP_FREE(temp_ptr);
                     return SDP_INVALID_PARAMETER;
 		}
 	    }
 	    tok = tmp;
 	    tok++;
 
             errno = 0;
             strtoul_result = strtoul(tok, &strtoul_end, 10);
 
             if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_fs", tok);
+                sdp_attr_fmtp_invalid_value(sdp_p, "max-fs", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
 	    }
 	    fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_fs = (u32) strtoul_result;
 	    codec_info_found = TRUE;
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[26].name,
                                sdp_fmtp_codec_param[26].strlen) == 0) {
@@ -1687,17 +1687,42 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
     	    if (errno || tok == strtoul_end || strtoul_result > 1) {
                 sdp_attr_fmtp_invalid_value(sdp_p, "cbr", tok);
     		SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
     	    }
     	    fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
     	    fmtp_p->cbr = (u16) strtoul_result;
     	    codec_info_found = TRUE;
-
+        } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[49].name,
+                                   sdp_fmtp_codec_param[49].strlen) == 0) {
+            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t",
+                                         &result1);
+            if (result1 != SDP_SUCCESS) {
+                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp),
+                                             " \t", &result1);
+                if (result1 != SDP_SUCCESS) {
+                    sdp_attr_fmtp_no_value(sdp_p, "max-fr");
+                    SDP_FREE(temp_ptr);
+                    return SDP_INVALID_PARAMETER;
+                }
+            }
+            tok = tmp;
+            tok++;
+            errno = 0;
+            strtoul_result = strtoul(tok, &strtoul_end, 10);
+            if (errno || tok == strtoul_end || strtoul_result == 0 ||
+                strtoul_result > UINT_MAX) {
+                sdp_attr_fmtp_invalid_value(sdp_p, "max-fr", tok);
+                SDP_FREE(temp_ptr);
+                return SDP_INVALID_PARAMETER;
+            }
+            fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
+            fmtp_p->max_fr = (u32) strtoul_result;
+            codec_info_found = TRUE;
         } else if (fmtp_ptr != NULL && *fmtp_ptr == '\n') {
             temp=PL_strtok_r(tmp, ";", &strtok_state);
             if (temp) {
                 if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
                     SDP_PRINT("%s Annexes are possibly there for this fmtp %s  tmp: %s line\n",
                               sdp_p->debug_str, fmtp_ptr, tmp);
                 }
                 while (temp != NULL) {
@@ -1994,16 +2019,18 @@ sdp_result_e sdp_build_attr_fmtp (sdp_t 
       FMTP_BUILD_UNSIGNED(fmtp_p->flag & SDP_INIT_BUF_TIME_FLAG,
         "sprop-init-buf-time", fmtp_p->init_buf_time)
 
       FMTP_BUILD_UNSIGNED(fmtp_p->max_mbps > 0,
         "max-mbps", fmtp_p->max_mbps)
 
       FMTP_BUILD_UNSIGNED(fmtp_p->max_fs > 0, "max-fs", fmtp_p->max_fs)
 
+      FMTP_BUILD_UNSIGNED(fmtp_p->max_fr > 0, "max-fr", fmtp_p->max_fr)
+
       FMTP_BUILD_UNSIGNED(fmtp_p->max_cpb > 0, "max-cpb", fmtp_p->max_cpb)
 
       FMTP_BUILD_UNSIGNED(fmtp_p->max_dpb > 0, "max-dpb", fmtp_p->max_dpb)
 
       FMTP_BUILD_UNSIGNED(fmtp_p->max_br > 0, "max-br", fmtp_p->max_br)
 
       FMTP_BUILD_UNSIGNED(fmtp_p->redundant_pic_cap > 0,
         "redundant-pic-cap", fmtp_p->redundant_pic_cap)
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr_access.c
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr_access.c
@@ -452,16 +452,17 @@ void sdp_copy_attr_fields (sdp_attr_t *s
 		 src_attr_p->attr.fmtp.init_buf_time;
         dst_attr_p->attr.fmtp.packetization_mode =
 		 src_attr_p->attr.fmtp.packetization_mode;
         dst_attr_p->attr.fmtp.flag =
                  src_attr_p->attr.fmtp.flag;
 
         dst_attr_p->attr.fmtp.max_mbps = src_attr_p->attr.fmtp.max_mbps;
         dst_attr_p->attr.fmtp.max_fs = src_attr_p->attr.fmtp.max_fs;
+        dst_attr_p->attr.fmtp.max_fr = src_attr_p->attr.fmtp.max_fr;
         dst_attr_p->attr.fmtp.max_cpb = src_attr_p->attr.fmtp.max_cpb;
         dst_attr_p->attr.fmtp.max_dpb = src_attr_p->attr.fmtp.max_dpb;
         dst_attr_p->attr.fmtp.max_br = src_attr_p->attr.fmtp.max_br;
         dst_attr_p->attr.fmtp.redundant_pic_cap =
             src_attr_p->attr.fmtp.redundant_pic_cap;
         dst_attr_p->attr.fmtp.deint_buf_cap =
 		 src_attr_p->attr.fmtp.deint_buf_cap;
         dst_attr_p->attr.fmtp.max_rcmd_nalu_size =
@@ -857,16 +858,17 @@ sdp_result_e sdp_copy_attr (void *src_sd
                  src_attr_p->attr.fmtp.init_buf_time;
         new_attr_p->attr.fmtp.packetization_mode =
                  src_attr_p->attr.fmtp.packetization_mode;
         new_attr_p->attr.fmtp.flag =
                  src_attr_p->attr.fmtp.flag;
 
         new_attr_p->attr.fmtp.max_mbps = src_attr_p->attr.fmtp.max_mbps;
         new_attr_p->attr.fmtp.max_fs = src_attr_p->attr.fmtp.max_fs;
+        new_attr_p->attr.fmtp.max_fr = src_attr_p->attr.fmtp.max_fr;
         new_attr_p->attr.fmtp.max_cpb = src_attr_p->attr.fmtp.max_cpb;
         new_attr_p->attr.fmtp.max_dpb = src_attr_p->attr.fmtp.max_dpb;
         new_attr_p->attr.fmtp.max_br = src_attr_p->attr.fmtp.max_br;
         new_attr_p->attr.fmtp.redundant_pic_cap =
             src_attr_p->attr.fmtp.redundant_pic_cap;
         new_attr_p->attr.fmtp.deint_buf_cap =
 		 src_attr_p->attr.fmtp.deint_buf_cap;
         new_attr_p->attr.fmtp.max_rcmd_nalu_size =
@@ -6356,16 +6358,49 @@ sdp_result_e sdp_attr_set_fmtp_max_fs (v
     if (max_fs > 0) {
         fmtp_p->max_fs  = max_fs;
         return (SDP_SUCCESS);
     } else {
         return (SDP_FAILURE);
     }
 }
 
+sdp_result_e sdp_attr_set_fmtp_max_fr (void *sdp_ptr, u16 level,
+                                       u8 cap_num, u16 inst_num,
+                                       u32 max_fr)
+{
+    sdp_t       *sdp_p = (sdp_t *)sdp_ptr;
+    sdp_attr_t  *attr_p;
+    sdp_fmtp_t  *fmtp_p;
+
+    if (sdp_verify_sdp_ptr(sdp_p) == FALSE) {
+        return (SDP_INVALID_PARAMETER);
+    }
+
+    attr_p = sdp_find_attr(sdp_p, level, cap_num, SDP_ATTR_FMTP, inst_num);
+    if (attr_p == NULL) {
+        if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
+            CSFLogError(logTag, "%s fmtp attribute, level %u instance %u "
+                      "not found.", sdp_p->debug_str, level, inst_num);
+        }
+        sdp_p->conf_p->num_invalid_param++;
+        return (SDP_INVALID_PARAMETER);
+    }
+
+    fmtp_p = &(attr_p->attr.fmtp);
+    fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
+
+    if (max_fr > 0) {
+        fmtp_p->max_fr  = max_fr;
+        return (SDP_SUCCESS);
+    } else {
+        return (SDP_FAILURE);
+    }
+}
+
 sdp_result_e sdp_attr_set_fmtp_max_br (void *sdp_ptr, u16 level,
                                        u8 cap_num, u16 inst_num,
                                        u32 max_br)
 {
     sdp_t       *sdp_p = (sdp_t *)sdp_ptr;
     sdp_attr_t  *attr_p;
     sdp_fmtp_t  *fmtp_p;
 
@@ -8260,16 +8295,51 @@ sdp_result_e sdp_attr_get_fmtp_max_fs (v
         sdp_p->conf_p->num_invalid_param++;
         return (SDP_INVALID_PARAMETER);
     } else {
         *val = attr_p->attr.fmtp.max_fs;
         return (SDP_SUCCESS);
     }
 }
 
+/* Function:    sdp_attr_get_fmtp_max_fr
+ * Description: Gets the value of the fmtp attribute- max-fr parameter
+ * Parameters:  sdp_ptr     The SDP handle returned by sdp_init_description.
+ *              level       The level to check for the attribute.
+ *              cap_num     The capability number associated with the
+ *                          attribute if any.  If none, should be zero.
+ *              inst_num    The attribute instance number to check.
+ * Returns:     max-fr value.
+ */
+
+sdp_result_e sdp_attr_get_fmtp_max_fr (void *sdp_ptr, u16 level,
+                             u8 cap_num, u16 inst_num, u32 *val)
+{
+    sdp_t       *sdp_p = (sdp_t *)sdp_ptr;
+    sdp_attr_t  *attr_p;
+
+    if (sdp_verify_sdp_ptr(sdp_p) == FALSE) {
+        return (SDP_INVALID_SDP_PTR);
+    }
+
+    attr_p = sdp_find_attr(sdp_p, level, cap_num, SDP_ATTR_FMTP,
+                           inst_num);
+    if (attr_p == NULL) {
+        if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
+            CSFLogError(logTag, "%s fmtp attribute, level %u instance %u "
+                      "not found.", sdp_p->debug_str, level, inst_num);
+        }
+        sdp_p->conf_p->num_invalid_param++;
+        return (SDP_INVALID_PARAMETER);
+    } else {
+        *val = attr_p->attr.fmtp.max_fr;
+        return (SDP_SUCCESS);
+    }
+}
+
 /* Function:    sdp_attr_get_fmtp_max_cpb
  * Description: Gets the value of the fmtp attribute- max-cpb parameter for H.264 codec
  * Parameters:  sdp_ptr     The SDP handle returned by sdp_init_description.
  *              level       The level to check for the attribute.
  *              cap_num     The capability number associated with the
  *                          attribute if any.  If none, should be zero.
  *              inst_num    The attribute instance number to check.
  * Returns:     max-cpb value.
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c
@@ -395,17 +395,18 @@ const sdp_namearray_t sdp_fmtp_codec_par
 
      {"mode",                sizeof("mode")},  /* 41 */
     {"level-asymmetry-allowed",         sizeof("level-asymmetry-allowed")}, /* 42 */
     {"maxaveragebitrate",               sizeof("maxaveragebitrate")}, /* 43 */
     {"usedtx",                          sizeof("usedtx")}, /* 44 */
     {"stereo",                          sizeof("stereo")}, /* 45 */
     {"useinbandfec",                    sizeof("useinbandfec")}, /* 46 */
     {"maxcodedaudiobandwidth",          sizeof("maxcodedaudiobandwidth")}, /* 47 */
-    {"cbr",                             sizeof("cbr")} /* 48 */
+    {"cbr",                             sizeof("cbr")}, /* 48 */
+    {"max-fr",                          sizeof("max-fr")} /* 49 */
 } ;
 
 /* Note: These *must* be in the same order as the enum type. */
 const sdp_namearray_t sdp_fmtp_codec_param_val[SDP_MAX_FMTP_PARAM_VAL] =
 {
     {"yes",                 sizeof("yes")},
     {"no",                  sizeof("no")}
 };
--- a/media/webrtc/signaling/src/sipcc/include/vcm.h
+++ b/media/webrtc/signaling/src/sipcc/include/vcm.h
@@ -181,16 +181,18 @@ typedef struct
       int bitrate;     /* Wire bitrate of RTP packet payloads */
     } audio;
 
     struct
     {
       int width;
       int height;
       uint32_t rtcp_fb_types;
+      uint32_t max_fs; /* Max frame size */
+      uint32_t max_fr; /* Max frame rate */
     } video;
   };
 
   /* Codec-specific parameters */
   union
   {
     struct {
         uint16_t mode;
@@ -1042,16 +1044,20 @@ int vcmOnSdpParseError(const char *peerc
 /**
  * vcmDisableRtcpComponent
  *
  * If we are doing rtcp-mux we need to disable component number 2 in the ICE
  * layer.  Otherwise we will wait for it to connect when it is unused
  */
 int vcmDisableRtcpComponent(const char *peerconnection, int level);
 
+short vcmGetVideoMaxFs(uint16_t codec, int32_t *max_fs);
+
+short vcmGetVideoMaxFr(uint16_t codec, int32_t *max_fs);
+
 //Using C++ for gips. This is the end of extern "C" above.
 #ifdef __cplusplus
 }
 #endif
 
 
 
 
--- a/media/webrtc/signaling/test/mediaconduit_unittests.cpp
+++ b/media/webrtc/signaling/test/mediaconduit_unittests.cpp
@@ -2,16 +2,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/. */
 
 #include <iostream>
 #include <string>
 #include <fstream>
 #include <unistd.h>
 #include <vector>
+#include <math.h>
 
 using namespace std;
 
 #include "mozilla/Scoped.h"
 #include <MediaConduitInterface.h>
 #include "nsIEventTarget.h"
 #include "FakeMediaStreamsImpl.h"
 
@@ -704,16 +705,162 @@ class TransportConduitTest : public ::te
     cerr << "    3. Null Codec Parameter  " << endl;
     cerr << "   *************************************************" << endl;
 
     err = mVideoSession->ConfigureSendMediaCodec(NULL);
     EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
 
   }
 
+  void DumpMaxFs(int orig_width, int orig_height, int max_fs,
+                 int new_width, int new_height)
+  {
+    cerr << "Applying max_fs=" << max_fs << " to input resolution " <<
+                 orig_width << "x" << orig_height << endl;
+    cerr << "New resolution: " << new_width << "x" << new_height << endl;
+    cerr << endl;
+  }
+
+  // Calculate new resolution for sending video by applying max-fs constraint.
+  void GetVideoResolutionWithMaxFs(int orig_width, int orig_height, int max_fs,
+                                   int *new_width, int *new_height)
+  {
+    int err = 0;
+
+    // Get pointer to VideoSessionConduit.
+    mVideoSession = mozilla::VideoSessionConduit::Create();
+    if( !mVideoSession )
+      ASSERT_NE(mVideoSession, (void*)NULL);
+
+    // Configure send codecs on the conduit.
+    mozilla::VideoCodecConfig cinst1(120, "VP8", 0, max_fs, 0);
+
+    err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
+    ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+    // Send one frame.
+    MOZ_ASSERT(!(orig_width & 1));
+    MOZ_ASSERT(!(orig_height & 1));
+    int len = ((orig_width * orig_height) * 3 / 2);
+    uint8_t* frame = (uint8_t*) PR_MALLOC(len);
+
+    memset(frame, COLOR, len);
+    mVideoSession->SendVideoFrame((unsigned char*)frame,
+                                  len,
+                                  orig_width,
+                                  orig_height,
+                                  mozilla::kVideoI420,
+                                  0);
+    PR_Free(frame);
+
+    // Get the new resolution as adjusted by the max-fs constraint.
+    *new_width = mVideoSession->SendingWidth();
+    *new_height = mVideoSession->SendingHeight();
+  }
+
+  void TestVideoConduitMaxFs()
+  {
+    int orig_width, orig_height, width, height, max_fs;
+
+    // No limitation.
+    cerr << "Test no max-fs limition" << endl;
+    orig_width = 640;
+    orig_height = 480;
+    max_fs = 0;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 640);
+    ASSERT_EQ(height, 480);
+
+    // VGA to QVGA.
+    cerr << "Test resizing from VGA to QVGA" << endl;
+    orig_width = 640;
+    orig_height = 480;
+    max_fs = 300;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 320);
+    ASSERT_EQ(height, 240);
+
+    // Extreme input resolution.
+    cerr << "Test extreme input resolution" << endl;
+    orig_width = 3072;
+    orig_height = 100;
+    max_fs = 300;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 768);
+    ASSERT_EQ(height, 26);
+
+    // Small max-fs.
+    cerr << "Test small max-fs (case 1)" << endl;
+    orig_width = 8;
+    orig_height = 32;
+    max_fs = 1;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 4);
+    ASSERT_EQ(height, 16);
+
+    // Small max-fs.
+    cerr << "Test small max-fs (case 2)" << endl;
+    orig_width = 4;
+    orig_height = 50;
+    max_fs = 1;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 2);
+    ASSERT_EQ(height, 16);
+
+    // Small max-fs.
+    cerr << "Test small max-fs (case 3)" << endl;
+    orig_width = 872;
+    orig_height = 136;
+    max_fs = 3;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 48);
+    ASSERT_EQ(height, 8);
+
+    // Small max-fs.
+    cerr << "Test small max-fs (case 4)" << endl;
+    orig_width = 160;
+    orig_height = 8;
+    max_fs = 5;
+    GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+    DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+    ASSERT_EQ(width, 80);
+    ASSERT_EQ(height, 4);
+
+    // Random values.
+    for (int i = 0; i < 30; i++) {
+      max_fs = rand() % 1000;
+      orig_width = ((rand() % 2000) & ~1) + 2;
+      orig_height = ((rand() % 2000) & ~1) + 2;
+      // Potential crash on small resolution, see bug 919979.
+      if (orig_width * orig_height <= 20) {
+        cerr << "Temporarily skip resolution " << orig_width << "x" <<
+             orig_height << endl;
+        continue;
+      }
+
+      GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs,
+                                  &width, &height);
+      if (max_fs > 0 &&
+          ceil(width / 16.) * ceil(height / 16.) > max_fs) {
+        DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+        ADD_FAILURE();
+      }
+      if ((width & 1) || (height & 1)) {
+        DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+        ADD_FAILURE();
+      }
+    }
+ }
+
 private:
   //Audio Conduit Test Objects
   mozilla::RefPtr<mozilla::AudioSessionConduit> mAudioSession;
   mozilla::RefPtr<mozilla::AudioSessionConduit> mAudioSession2;
   mozilla::RefPtr<mozilla::TransportInterface> mAudioTransport;
   AudioSendAndReceive audioTester;
 
   //Video Conduit Test Objects
@@ -739,16 +886,20 @@ TEST_F(TransportConduitTest, TestDummyAu
 TEST_F(TransportConduitTest, TestDummyVideoWithTransport) {
   TestDummyVideoAndTransport();
  }
 
 TEST_F(TransportConduitTest, TestVideoConduitCodecAPI) {
   TestVideoConduitCodecAPI();
  }
 
+TEST_F(TransportConduitTest, TestVideoConduitMaxFs) {
+  TestVideoConduitMaxFs();
+ }
+
 }  // end namespace
 
 int main(int argc, char **argv)
 {
   // This test can cause intermittent oranges on the builders
   CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_MEDIACONDUIT_TESTS")
 
   test_utils = new MtransportTestUtils();
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -185,16 +185,51 @@ class SdpTest : public ::testing::Test {
       u16 inst_num = 0;
       EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
                                  &inst_num), SDP_SUCCESS);
       EXPECT_EQ(sdp_attr_set_rtcp_fb_ccm(sdp_ptr_, level, payload, inst_num,
                                          type), SDP_SUCCESS);
       return inst_num;
     }
 
+    u16 AddNewFmtpMaxFs(int level, u32 max_fs) {
+      u16 inst_num = 0;
+      EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+                                 &inst_num), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+                                               120), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+                                         SDP_SUCCESS);
+      return inst_num;
+    }
+
+    u16 AddNewFmtpMaxFr(int level, u32 max_fr) {
+      u16 inst_num = 0;
+      EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+                                 &inst_num), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+                                               120), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+                                         SDP_SUCCESS);
+      return inst_num;
+    }
+
+     u16 AddNewFmtpMaxFsFr(int level, u32 max_fs, u32 max_fr) {
+      u16 inst_num = 0;
+      EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+                                 &inst_num), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+                                               120), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+                                         SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+                                         SDP_SUCCESS);
+      return inst_num;
+    }
+
   protected:
     int final_level_;
     void *config_p_;
     sdp_t *sdp_ptr_;
 };
 
 static const std::string kVideoSdp =
   "v=0\r\n"
@@ -684,16 +719,55 @@ TEST_F(SdpTest, addRtcpFbCcmVbcmAllPt) {
 TEST_F(SdpTest, parseRtcpFbAllPayloads) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:* ack rpsi\r\n");
   for (int i = 0; i < 128; i++) {
     ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, i, 1),
               SDP_RTCP_FB_ACK_RPSI);
   }
 }
 
+TEST_F(SdpTest, parseFmtpMaxFs) {
+  u32 val = 0;
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+  ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+  ASSERT_EQ(val, 300);
+}
+
+TEST_F(SdpTest, parseFmtpMaxFr) {
+  u32 val = 0;
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+  ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+  ASSERT_EQ(val, 30);
+}
+
+TEST_F(SdpTest, addFmtpMaxFs) {
+  InitLocalSdp();
+  int level = AddNewMedia(SDP_MEDIA_VIDEO);
+  AddNewFmtpMaxFs(level, 300);
+  std::string body = SerializeSdp();
+  ASSERT_NE(body.find("a=fmtp:120 max-fs=300\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFr) {
+  InitLocalSdp();
+  int level = AddNewMedia(SDP_MEDIA_VIDEO);
+  AddNewFmtpMaxFr(level, 30);
+  std::string body = SerializeSdp();
+  ASSERT_NE(body.find("a=fmtp:120 max-fr=30\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFsFr) {
+  InitLocalSdp();
+  int level = AddNewMedia(SDP_MEDIA_VIDEO);
+  AddNewFmtpMaxFsFr(level, 300, 30);
+  std::string body = SerializeSdp();
+  ASSERT_NE(body.find("a=fmtp:120 max-fs=300;max-fr=30\r\n"),
+            std::string::npos);
+}
+
 } // End namespace test.
 
 int main(int argc, char **argv) {
   test_utils = new MtransportTestUtils();
   NSS_NoDB_Init(NULL);
   NSS_SetDomesticPolicy();
 
   ::testing::InitGoogleTest(&argc, argv);
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -21,16 +21,19 @@
 #include "prthread.h"
 
 #include "FakeMediaStreams.h"
 #include "FakeMediaStreamsImpl.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionCtx.h"
 #include "runnable_utils.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
 #include "nsNetUtil.h"
 #include "nsIIOService.h"
 #include "nsIDNSService.h"
 #include "nsWeakReference.h"
 #include "nricectx.h"
 #include "mozilla/SyncRunnable.h"
 #include "logging.h"
 #include "stunserver.h"
@@ -109,38 +112,43 @@ static const unsigned short nSamplelevel
 enum sdpTestFlags
 {
   SHOULD_SEND_AUDIO     = (1<<0),
   SHOULD_RECV_AUDIO     = (1<<1),
   SHOULD_INACTIVE_AUDIO = (1<<2),
   SHOULD_REJECT_AUDIO   = (1<<3),
   SHOULD_OMIT_AUDIO     = (1<<4),
   DONT_CHECK_AUDIO      = (1<<5),
+  SHOULD_CHECK_AUDIO    = (1<<6),
 
   SHOULD_SEND_VIDEO     = (1<<8),
   SHOULD_RECV_VIDEO     = (1<<9),
   SHOULD_INACTIVE_VIDEO = (1<<10),
   SHOULD_REJECT_VIDEO   = (1<<11),
   SHOULD_OMIT_VIDEO     = (1<<12),
   DONT_CHECK_VIDEO      = (1<<13),
+  SHOULD_CHECK_VIDEO    = (1<<14),
 
   SHOULD_INCLUDE_DATA   = (1 << 16),
   DONT_CHECK_DATA       = (1 << 17),
 
   SHOULD_SENDRECV_AUDIO = SHOULD_SEND_AUDIO | SHOULD_RECV_AUDIO,
   SHOULD_SENDRECV_VIDEO = SHOULD_SEND_VIDEO | SHOULD_RECV_VIDEO,
   SHOULD_SENDRECV_AV = SHOULD_SENDRECV_AUDIO | SHOULD_SENDRECV_VIDEO,
+  SHOULD_CHECK_AV = SHOULD_CHECK_AUDIO | SHOULD_CHECK_VIDEO,
 
   AUDIO_FLAGS = SHOULD_SEND_AUDIO | SHOULD_RECV_AUDIO
                 | SHOULD_INACTIVE_AUDIO | SHOULD_REJECT_AUDIO
-                | DONT_CHECK_AUDIO | SHOULD_OMIT_AUDIO,
+                | DONT_CHECK_AUDIO | SHOULD_OMIT_AUDIO
+                | SHOULD_CHECK_AUDIO,
 
   VIDEO_FLAGS = SHOULD_SEND_VIDEO | SHOULD_RECV_VIDEO
                 | SHOULD_INACTIVE_VIDEO | SHOULD_REJECT_VIDEO
                 | DONT_CHECK_VIDEO | SHOULD_OMIT_VIDEO
+                | SHOULD_CHECK_VIDEO
 };
 
 enum offerAnswerFlags
 {
   OFFER_NONE  = 0, // Sugar to make function calls clearer.
   OFFER_AUDIO = (1<<0),
   OFFER_VIDEO = (1<<1),
   // Leaving some room here for other media types
@@ -1146,16 +1154,22 @@ private:
               << ((flags & SHOULD_INCLUDE_DATA)?" SHOULD_INCLUDE_DATA":"")
               << ((flags & DONT_CHECK_DATA)?" DONT_CHECK_DATA":"")
               << std::endl;
 
     switch(flags & AUDIO_FLAGS) {
       case 0:
             ASSERT_EQ(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
         break;
+      case SHOULD_CHECK_AUDIO:
+            ASSERT_NE(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
+            if (offer) {
+              ASSERT_NE(sdp.find("a=rtpmap:0 PCMU/8000"), std::string::npos);
+            }
+        break;
       case SHOULD_SEND_AUDIO:
             ASSERT_NE(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
             ASSERT_NE(sdp.find(" 0-15\r\na=sendonly"), std::string::npos);
             if (offer) {
               ASSERT_NE(sdp.find("a=rtpmap:0 PCMU/8000"), std::string::npos);
             }
         break;
       case SHOULD_RECV_AUDIO:
@@ -1188,16 +1202,19 @@ private:
       default:
             ASSERT_FALSE("Missing case in switch statement");
     }
 
     switch(flags & VIDEO_FLAGS) {
       case 0:
             ASSERT_EQ(sdp.find("a=rtpmap:120 VP8/90000"), std::string::npos);
         break;
+      case SHOULD_CHECK_VIDEO:
+            ASSERT_NE(sdp.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+        break;
       case SHOULD_SEND_VIDEO:
             ASSERT_NE(sdp.find("a=rtpmap:120 VP8/90000\r\na=sendonly"),
                   std::string::npos);
         break;
       case SHOULD_RECV_VIDEO:
             ASSERT_NE(sdp.find("a=rtpmap:120 VP8/90000\r\na=recvonly"),
                   std::string::npos);
         break;
@@ -1535,25 +1552,80 @@ public:
     stun_addr_ = TestStunServer::GetInstance()->addr();
     stun_port_ = TestStunServer::GetInstance()->port();
 
     TestStunServer::GetInstance()->SetActive(false);
     TestStunServer::GetInstance()->SetResponseAddr(
         kBogusSrflxAddress, kBogusSrflxPort);
   }
 
+  // Check max-fs and max-fr in SDP
+  void CheckMaxFsFrSdp(const std::string sdp,
+                       int format,
+                       int max_fs,
+                       int max_fr) {
+    ParsedSDP sdpWrapper(sdp);
+    std::stringstream ss;
+    ss << "a=fmtp:" << format;
+    std::vector<std::string> lines = sdpWrapper.GetLines(ss.str());
+
+    // Both max-fs and max-fr not exist
+    if (lines.empty()) {
+      ASSERT_EQ(max_fs, 0);
+      ASSERT_EQ(max_fr, 0);
+      return;
+    }
+
+    // At most one instance allowed for each format
+    ASSERT_EQ(lines.size(), 1U);
+
+    std::string line = lines.front();
+
+    // Make sure that max-fs doesn't exist
+    if (max_fs == 0) {
+      ASSERT_EQ(line.find("max-fs="), std::string::npos);
+    }
+    // Check max-fs value
+    if (max_fs > 0) {
+      std::stringstream ss;
+      ss << "max-fs=" << max_fs;
+      ASSERT_NE(line.find(ss.str()), std::string::npos);
+    }
+    // Make sure that max-fr doesn't exist
+    if (max_fr == 0) {
+      ASSERT_EQ(line.find("max-fr="), std::string::npos);
+    }
+    // Check max-fr value
+    if (max_fr > 0) {
+      std::stringstream ss;
+      ss << "max-fr=" << max_fr;
+      ASSERT_NE(line.find(ss.str()), std::string::npos);
+    }
+  }
+
  protected:
   bool init_;
   ScopedDeletePtr<SignalingAgent> a1_;  // Canonically "caller"
   ScopedDeletePtr<SignalingAgent> a2_;  // Canonically "callee"
   bool wait_for_gather_;
   std::string stun_addr_;
   uint16_t stun_port_;
 };
 
+class FsFrPrefClearer {
+  public:
+    FsFrPrefClearer(nsCOMPtr<nsIPrefBranch> prefs): mPrefs(prefs) {}
+    ~FsFrPrefClearer() {
+      mPrefs->ClearUserPref("media.navigator.video.max_fs");
+      mPrefs->ClearUserPref("media.navigator.video.max_fr");
+    }
+  private:
+    nsCOMPtr<nsIPrefBranch> mPrefs;
+};
+
 TEST_F(SignalingTest, JustInit)
 {
 }
 
 TEST_F(SignalingTest, CreateSetOffer)
 {
   sipcc::MediaConstraints constraints;
   CreateSetOffer(constraints, SHOULD_SENDRECV_AV);
@@ -3323,16 +3395,177 @@ TEST_F(SignalingTest, hugeSdp)
   a1_->CreateOffer(constraints, OFFER_AV, SHOULD_SENDRECV_AV);
   a1_->SetLocal(TestObserver::OFFER, offer, true);
 
   a2_->SetRemote(TestObserver::OFFER, offer, true);
   ASSERT_GE(a2_->getRemoteDescription().length(), 4096U);
   a2_->CreateAnswer(constraints, offer, OFFER_AV);
 }
 
+// Test max_fs and max_fr prefs have proper impact on SDP offer
+TEST_F(SignalingTest, MaxFsFrInOffer)
+{
+  EnsureInit();
+
+  sipcc::MediaConstraints constraints;
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  ASSERT_TRUE(prefs);
+  FsFrPrefClearer prefClearer(prefs);
+
+  prefs->SetIntPref("media.navigator.video.max_fs", 300);
+  prefs->SetIntPref("media.navigator.video.max_fr", 30);
+
+  a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
+
+  // Verify that SDP contains correct max-fs and max-fr
+  CheckMaxFsFrSdp(a1_->offer(), 120, 300, 30);
+}
+
+// Test max_fs and max_fr prefs have proper impact on SDP answer
+TEST_F(SignalingTest, MaxFsFrInAnswer)
+{
+  EnsureInit();
+
+  sipcc::MediaConstraints constraints;
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  ASSERT_TRUE(prefs);
+  FsFrPrefClearer prefClearer(prefs);
+
+  // We don't want max_fs and max_fr prefs impact SDP at this moment
+  prefs->SetIntPref("media.navigator.video.max_fs", 0);
+  prefs->SetIntPref("media.navigator.video.max_fr", 0);
+
+  a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
+
+  // SDP should not contain max-fs and max-fr here
+  CheckMaxFsFrSdp(a1_->offer(), 120, 0, 0);
+
+  a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+
+  prefs->SetIntPref("media.navigator.video.max_fs", 600);
+  prefs->SetIntPref("media.navigator.video.max_fr", 60);
+
+  a2_->CreateAnswer(constraints, a1_->offer(), OFFER_AV | ANSWER_AV);
+
+  // Verify that SDP contains correct max-fs and max-fr
+  CheckMaxFsFrSdp(a2_->answer(), 120, 600, 60);
+}
+
+// Test SDP offer has proper impact on callee's codec configuration
+TEST_F(SignalingTest, MaxFsFrCalleeCodec)
+{
+  EnsureInit();
+
+  sipcc::MediaConstraints constraints;
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  ASSERT_TRUE(prefs);
+  FsFrPrefClearer prefClearer(prefs);
+
+  // We don't want max_fs and max_fr prefs impact SDP at this moment
+  prefs->SetIntPref("media.navigator.video.max_fs", 0);
+  prefs->SetIntPref("media.navigator.video.max_fr", 0);
+
+  a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
+
+  ParsedSDP sdpWrapper(a1_->offer());
+
+  sdpWrapper.ReplaceLine("a=rtpmap:120",
+    "a=rtpmap:120 VP8/90000\r\na=fmtp:120 max-fs=300;max-fr=30\r\n");
+
+  std::cout << "Modified SDP " << std::endl
+            << indent(sdpWrapper.getSdp()) << std::endl;
+
+  // Double confirm that SDP offer contains correct max-fs and max-fr
+  CheckMaxFsFrSdp(sdpWrapper.getSdp(), 120, 300, 30);
+
+  a1_->SetLocal(TestObserver::OFFER, sdpWrapper.getSdp());
+  a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp());
+
+  a2_->CreateAnswer(constraints, sdpWrapper.getSdp(), OFFER_AV | ANSWER_AV);
+
+  // SDP should not contain max-fs and max-fr here
+  CheckMaxFsFrSdp(a2_->answer(), 120, 0, 0);
+
+  a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+  a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
+
+  ASSERT_TRUE_WAIT(a1_->IceCompleted() == true, kDefaultTimeout);
+  ASSERT_TRUE_WAIT(a2_->IceCompleted() == true, kDefaultTimeout);
+
+  // Checking callee's video sending configuration does respect max-fs and
+  // max-fr in SDP offer.
+  mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
+    a2_->GetMediaPipeline(1, 0, 1);
+  ASSERT_TRUE(pipeline);
+  mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
+  ASSERT_TRUE(conduit);
+  ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
+  mozilla::VideoSessionConduit *video_conduit =
+    static_cast<mozilla::VideoSessionConduit*>(conduit);
+
+  ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 300);
+  ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 30);
+}
+
+// Test SDP answer has proper impact on caller's codec configuration
+TEST_F(SignalingTest, MaxFsFrCallerCodec)
+{
+  EnsureInit();
+
+  sipcc::MediaConstraints constraints;
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  ASSERT_TRUE(prefs);
+  FsFrPrefClearer prefClearer(prefs);
+
+  // We don't want max_fs and max_fr prefs impact SDP at this moment
+  prefs->SetIntPref("media.navigator.video.max_fs", 0);
+  prefs->SetIntPref("media.navigator.video.max_fr", 0);
+
+  a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
+  a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+  a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+
+  a2_->CreateAnswer(constraints, a1_->offer(), OFFER_AV | ANSWER_AV);
+
+  ParsedSDP sdpWrapper(a2_->answer());
+
+  sdpWrapper.ReplaceLine("a=rtpmap:120",
+    "a=rtpmap:120 VP8/90000\r\na=fmtp:120 max-fs=600;max-fr=60\r\n");
+
+  std::cout << "Modified SDP " << std::endl
+            << indent(sdpWrapper.getSdp()) << std::endl;
+
+  // Double confirm that SDP answer contains correct max-fs and max-fr
+  CheckMaxFsFrSdp(sdpWrapper.getSdp(), 120, 600, 60);
+
+  a2_->SetLocal(TestObserver::ANSWER, sdpWrapper.getSdp());
+  a1_->SetRemote(TestObserver::ANSWER, sdpWrapper.getSdp());
+
+  ASSERT_TRUE_WAIT(a1_->IceCompleted() == true, kDefaultTimeout);
+  ASSERT_TRUE_WAIT(a2_->IceCompleted() == true, kDefaultTimeout);
+
+  // Checking caller's video sending configuration does respect max-fs and
+  // max-fr in SDP answer.
+  mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
+    a1_->GetMediaPipeline(1, 0, 1);
+  ASSERT_TRUE(pipeline);
+  mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
+  ASSERT_TRUE(conduit);
+  ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
+  mozilla::VideoSessionConduit *video_conduit =
+    static_cast<mozilla::VideoSessionConduit*>(conduit);
+
+  ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 600);
+  ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 60);
+}
+
 } // End namespace test.
 
 bool is_color_terminal(const char *terminal) {
   if (!terminal) {
     return false;
   }
   const char *color_terms[] = {
     "xterm",
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -782,8 +782,10 @@ pref("security.csp.speccompliant", true)
 pref("gfx.canvas.azure.backends", "skia");
 pref("gfx.canvas.azure.accelerated", true);
 
 pref("general.useragent.override.youtube.com", "Android; Tablet;#Android; Mobile;");
 
 // When true, phone number linkification is enabled.
 pref("browser.ui.linkify.phone", false);
 
+// Enables/disables Spatial Navigation
+pref("snav.enabled", true);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -13,16 +13,17 @@ let Cr = Components.results;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/JNI.jsm");
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
 Cu.import("resource://gre/modules/ContactService.jsm");
+Cu.import("resource://gre/modules/SpatialNavigation.jsm");
 
 #ifdef ACCESSIBILITY
 Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
@@ -4106,16 +4107,19 @@ var BrowserEventHandler = {
     Services.obs.addObserver(this, "Gesture:DoubleTap", false);
     Services.obs.addObserver(this, "Gesture:Scroll", false);
     Services.obs.addObserver(this, "dom-touch-listener-added", false);
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
     BrowserApp.deck.addEventListener("touchstart", this, true);
     BrowserApp.deck.addEventListener("click", InputWidgetHelper, true);
     BrowserApp.deck.addEventListener("click", SelectHelper, true);
+
+    SpatialNavigation.init(BrowserApp.deck, null);
+
     document.addEventListener("MozMagnifyGesture", this, true);
 
     Services.prefs.addObserver("browser.zoom.reflowOnZoom", this, false);
     this.updateReflozPref();
   },
 
   resetMaxLineBoxWidth: function() {
     BrowserApp.selectedTab.probablyNeedRefloz = false;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -225,16 +225,18 @@ pref("media.gstreamer.enabled", true);
 pref("media.apple.mp3.enabled", true);
 #endif
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", true);
 pref("media.navigator.video.default_width",640);
 pref("media.navigator.video.default_height",480);
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.default_minfps",10);
+pref("media.navigator.video.max_fs", 0); // unrestricted
+pref("media.navigator.video.max_fr", 0); // unrestricted
 pref("media.peerconnection.enabled", true);
 pref("media.navigator.permission.disabled", false);
 pref("media.peerconnection.default_iceservers", "[{\"url\": \"stun:23.21.150.121\"}]");
 pref("media.peerconnection.trickle_ice", true);
 pref("media.peerconnection.use_document_iceservers", true);
 // These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h
 // kXxxUnchanged = 0, kXxxDefault = 1, and higher values are specific to each 
 // setting (for Xxx = Ec, Agc, or Ns).  Defaults are all set to kXxxDefault here.
@@ -4459,8 +4461,11 @@ pref("dom.telephony.enabled", false);
 // DOM Inter-App Communication API.
 pref("dom.inter-app-communication-api.enabled", false);
 
 // The tables used for Safebrowsing phishing and malware checks.
 pref("urlclassifier.malware_table", "goog-malware-shavar");
 pref("urlclassifier.phish_table", "goog-phish-shavar");
 pref("urlclassifier.download_block_table", "goog-badbinurl-shavar");
 pref("urlclassifier.download_allow_table", "goog-downloadwhite-digest256");
+
+// Turn off Spatial navigation by default.
+pref("snav.enabled", false);
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/SpatialNavigation.jsm
@@ -0,0 +1,505 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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 this module through
+ *
+ * Components.utils.import("resource://gre/modules/SpatialNavigation.jsm");
+ *
+ * Usage: (Literal class)
+ *
+ * SpatialNavigation.init(browser_element, optional_callback);
+ *
+ * optional_callback will be called when a new element is focused.
+ *
+ *    function optional_callback(element) {}
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SpatialNavigation"];
+
+var SpatialNavigation = {
+  init: function(browser, callback) {
+          browser.addEventListener("keydown", function (event) {
+            _onInputKeyPress(event, callback);
+          }, true);
+  }
+};
+
+// Private stuff
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu["import"]("resource://gre/modules/Services.jsm", this);
+
+let eventListenerService = Cc["@mozilla.org/eventlistenerservice;1"]
+                             .getService(Ci.nsIEventListenerService);
+let focusManager         = Cc["@mozilla.org/focus-manager;1"]
+                             .getService(Ci.nsIFocusManager);
+let windowMediator       = Cc['@mozilla.org/appshell/window-mediator;1']
+                             .getService(Ci.nsIWindowMediator);
+
+// Debug helpers:
+function dump(a) {
+  Services.console.logStringMessage("SpatialNavigation: " + a);
+}
+
+function dumpRect(desc, rect) {
+  dump(desc + " " + Math.round(rect.left) + " " + Math.round(rect.top) + " " +
+       Math.round(rect.right) + " " + Math.round(rect.bottom) + " width:" +
+       Math.round(rect.width) + " height:" + Math.round(rect.height));
+}
+
+function dumpNodeCoord(desc, node) {
+  let rect = node.getBoundingClientRect();
+  dump(desc + " " + node.tagName + " x:" + Math.round(rect.left + rect.width/2) +
+       " y:" + Math.round(rect.top + rect.height / 2));
+}
+
+// modifier values
+
+const kAlt   = "alt";
+const kShift = "shift";
+const kCtrl  = "ctrl";
+const kNone  = "none";
+
+function _onInputKeyPress (event, callback) {
+  //If Spatial Navigation isn't enabled, return.
+  if (!PrefObserver['enabled']) {
+    return;
+  }
+
+  // Use whatever key value is available (either keyCode or charCode).
+  // It might be useful for addons or whoever wants to set different
+  // key to be used here (e.g. "a", "F1", "arrowUp", ...).
+  var key = event.which || event.keyCode;
+
+  if (key != PrefObserver['keyCodeDown']  &&
+      key != PrefObserver['keyCodeRight'] &&
+      key != PrefObserver['keyCodeUp'] &&
+      key != PrefObserver['keyCodeLeft'] &&
+      key != PrefObserver['keyCodeReturn']) {
+    return;
+  }
+
+  if (key == PrefObserver['keyCodeReturn']) {
+    // We report presses of the action button on a gamepad "A" as the return
+    // key to the DOM. The behaviour of hitting the return key and clicking an
+    // element is the same for some elements, but not all, so we handle the
+    // ones we want (like the Select element) here:
+    if (event.target instanceof Ci.nsIDOMHTMLSelectElement &&
+        event.target.click) {
+      event.target.click();
+      event.stopPropagation();
+      event.preventDefault();
+      return;
+    } else {
+      // Leave the action key press to get reported to the DOM as a return
+      // keypress.
+      return;
+    }
+  }
+
+  // If it is not using the modifiers it should, return.
+  if (!event.altKey && PrefObserver['modifierAlt'] ||
+      !event.shiftKey && PrefObserver['modifierShift'] ||
+      !event.crtlKey && PrefObserver['modifierCtrl']) {
+    return;
+  }
+
+  let currentlyFocused = event.target;
+  let currentlyFocusedWindow = currentlyFocused.ownerDocument.defaultView;
+  let bestElementToFocus = null;
+
+  // If currentlyFocused is an nsIDOMHTMLBodyElement then the page has just been
+  // loaded, and this is the first keypress in the page.
+  if (currentlyFocused instanceof Ci.nsIDOMHTMLBodyElement) {
+    focusManager.moveFocus(currentlyFocusedWindow, null, focusManager.MOVEFOCUS_FIRST, 0);
+    event.stopPropagation();
+    event.preventDefault();
+    return;
+  }
+
+  let windowUtils = currentlyFocusedWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                          .getInterface(Ci.nsIDOMWindowUtils);
+  let cssPageRect = _getRootBounds(windowUtils);
+  let searchRect = _getSearchRect(currentlyFocused, key, cssPageRect);
+
+  let nodes = {};
+  nodes.length = 0;
+
+  let searchRectOverflows = false;
+
+  while (!bestElementToFocus && !searchRectOverflows) {
+    switch (key) {
+      case PrefObserver['keyCodeLeft']:
+      case PrefObserver['keyCodeRight']: {
+        if (searchRect.top < cssPageRect.top &&
+            searchRect.bottom > cssPageRect.bottom) {
+          searchRectOverflows = true;
+        }
+        break;
+      }
+      case PrefObserver['keyCodeUp']:
+      case PrefObserver['keyCodeDown']: {
+        if (searchRect.left < cssPageRect.left &&
+            searchRect.right > cssPageRect.right) {
+          searchRectOverflows = true;
+        }
+        break;
+      }
+    }
+
+    nodes = windowUtils.nodesFromRect(searchRect.left, searchRect.top,
+                                      0, searchRect.width, searchRect.height, 0,
+                                      true, false);
+    // Make the search rectangle "wider": double it's size in the direction
+    // that is not the keypress.
+    switch (key) {
+      case PrefObserver['keyCodeLeft']:
+      case PrefObserver['keyCodeRight']: {
+        searchRect.top = searchRect.top - (searchRect.height / 2);
+        searchRect.bottom = searchRect.top + (searchRect.height * 2);
+        searchRect.height = searchRect.height * 2;
+        break;
+      }
+      case PrefObserver['keyCodeUp']:
+      case PrefObserver['keyCodeDown']: {
+        searchRect.left = searchRect.left - (searchRect.width / 2);
+        searchRect.right = searchRect.left + (searchRect.width * 2);
+        searchRect.width = searchRect.width * 2;
+        break;
+      }
+    }
+    bestElementToFocus = _getBestToFocus(nodes, key, currentlyFocused);
+  }
+
+
+  if (bestElementToFocus === null) {
+    // Couldn't find an element to focus.
+    return;
+  }
+
+  focusManager.setFocus(bestElementToFocus, focusManager.FLAG_SHOWRING);
+
+  //if it is a text element, select all.
+  if ((bestElementToFocus instanceof Ci.nsIDOMHTMLInputElement &&
+       bestElementToFocus.mozIsTextField(false)) ||
+      bestElementToFocus instanceof Ci.nsIDOMHTMLTextAreaElement) {
+    bestElementToFocus.selectionStart = 0;
+    bestElementToFocus.selectionEnd = bestElementToFocus.textLength;
+  }
+
+  if (callback != undefined) {
+    callback(bestElementToFocus);
+  }
+
+  event.preventDefault();
+  event.stopPropagation();
+}
+
+// Returns the bounds of the page relative to the viewport.
+function _getRootBounds(windowUtils) {
+  let cssPageRect = windowUtils.getRootBounds();
+
+  let scrollX = {};
+  let scrollY = {};
+  windowUtils.getScrollXY(false, scrollX, scrollY);
+
+  let cssPageRectCopy = {};
+
+  cssPageRectCopy.right = cssPageRect.right - scrollX.value;
+  cssPageRectCopy.left = cssPageRect.left - scrollX.value;
+  cssPageRectCopy.top = cssPageRect.top - scrollY.value;
+  cssPageRectCopy.bottom = cssPageRect.bottom - scrollY.value;
+  cssPageRectCopy.width = cssPageRect.width;
+  cssPageRectCopy.height = cssPageRect.height;
+
+  return cssPageRectCopy;
+}
+
+// Returns the best node to focus from the list of nodes returned by the hit
+// test.
+function _getBestToFocus(nodes, key, currentlyFocused) {
+  let best = null;
+  let bestDist;
+  let bestMid;
+  let nodeMid;
+  let currentlyFocusedMid = _getMidpoint(currentlyFocused);
+  let currentlyFocusedRect = currentlyFocused.getBoundingClientRect();
+
+  for (let i = 0; i < nodes.length; i++) {
+    // Reject the currentlyFocused, and all node types we can't focus
+    if (!_canFocus(nodes[i]) || nodes[i] === currentlyFocused) {
+      continue;
+    }
+
+    // Reject all nodes that aren't "far enough" in the direction of the
+    // keypress
+    nodeMid = _getMidpoint(nodes[i]);
+    switch (key) {
+      case PrefObserver['keyCodeLeft']:
+        if (nodeMid.x >= (currentlyFocusedMid.x - currentlyFocusedRect.width / 2)) {
+          continue;
+        }
+        break;
+      case PrefObserver['keyCodeRight']:
+        if (nodeMid.x <= (currentlyFocusedMid.x + currentlyFocusedRect.width / 2)) {
+          continue;
+        }
+        break;
+
+      case PrefObserver['keyCodeUp']:
+        if (nodeMid.y >= (currentlyFocusedMid.y - currentlyFocusedRect.height / 2)) {
+          continue;
+        }
+        break;
+      case PrefObserver['keyCodeDown']:
+        if (nodeMid.y <= (currentlyFocusedMid.y + currentlyFocusedRect.height / 2)) {
+          continue;
+        }
+        break;
+    }
+
+    // Initialize best to the first viable value:
+    if (!best) {
+      best = nodes[i];
+      bestDist = _spatialDistance(best, currentlyFocused);
+      continue;
+    }
+
+    // Of the remaining nodes, pick the one closest to the currently focused
+    // node.
+    let curDist = _spatialDistance(nodes[i], currentlyFocused);
+    if (curDist > bestDist) {
+      continue;
+    }
+
+    bestMid = _getMidpoint(best);
+    switch (key) {
+      case PrefObserver['keyCodeLeft']:
+        if (nodeMid.x > bestMid.x) {
+          best = nodes[i];
+          bestDist = curDist;
+        }
+        break;
+      case PrefObserver['keyCodeRight']:
+        if (nodeMid.x < bestMid.x) {
+          best = nodes[i];
+          bestDist = curDist;
+        }
+        break;
+      case PrefObserver['keyCodeUp']:
+        if (nodeMid.y > bestMid.y) {
+          best = nodes[i];
+          bestDist = curDist;
+        }
+        break;
+      case PrefObserver['keyCodeDown']:
+        if (nodeMid.y < bestMid.y) {
+          best = nodes[i];
+          bestDist = curDist;
+        }
+        break;
+    }
+  }
+  return best;
+}
+
+// Returns the midpoint of a node.
+function _getMidpoint(node) {
+  let mid = {};
+  let box = node.getBoundingClientRect();
+  mid.x = box.left + (box.width / 2);
+  mid.y = box.top + (box.height / 2);
+
+  return mid;
+}
+
+// Returns true if the node is a type that we want to focus, false otherwise.
+function _canFocus(node) {
+  if (node instanceof Ci.nsIDOMHTMLLinkElement ||
+      node instanceof Ci.nsIDOMHTMLAnchorElement) {
+    return true;
+  }
+  if ((node instanceof Ci.nsIDOMHTMLButtonElement ||
+        node instanceof Ci.nsIDOMHTMLInputElement ||
+        node instanceof Ci.nsIDOMHTMLLinkElement ||
+        node instanceof Ci.nsIDOMHTMLOptGroupElement ||
+        node instanceof Ci.nsIDOMHTMLSelectElement ||
+        node instanceof Ci.nsIDOMHTMLTextAreaElement) &&
+      node.disabled === false) {
+    return true;
+  }
+  return false;
+}
+
+// Returns a rectangle that extends to the end of the screen in the direction that
+// the key is pressed.
+function _getSearchRect(currentlyFocused, key, cssPageRect) {
+  let currentlyFocusedRect = currentlyFocused.getBoundingClientRect();
+
+  let newRect = {};
+  newRect.left   = currentlyFocusedRect.left;
+  newRect.top    = currentlyFocusedRect.top;
+  newRect.right  = currentlyFocusedRect.right;
+  newRect.bottom = currentlyFocusedRect.bottom;
+  newRect.width  = currentlyFocusedRect.width;
+  newRect.height = currentlyFocusedRect.height;
+
+  switch (key) {
+    case PrefObserver['keyCodeLeft']:
+      newRect.left = cssPageRect.left;
+      newRect.width = newRect.right - newRect.left;
+      break;
+
+    case PrefObserver['keyCodeRight']:
+      newRect.right = cssPageRect.right;
+      newRect.width = newRect.right - newRect.left;
+      break;
+
+    case PrefObserver['keyCodeUp']:
+      newRect.top = cssPageRect.top;
+      newRect.height = newRect.bottom - newRect.top;
+      break;
+
+    case PrefObserver['keyCodeDown']:
+      newRect.bottom = cssPageRect.bottom;
+      newRect.height = newRect.bottom - newRect.top;
+      break;
+  }
+  return newRect;
+}
+
+// Gets the distance between two points a and b.
+function _spatialDistance(a, b) {
+  let mida = _getMidpoint(a);
+  let midb = _getMidpoint(b);
+
+  return Math.round(Math.pow(mida.x - midb.x, 2) +
+                    Math.pow(mida.y - midb.y, 2));
+}
+
+// Snav preference observer
+var PrefObserver = {
+  register: function() {
+    this.prefService = Cc["@mozilla.org/preferences-service;1"]
+                          .getService(Ci.nsIPrefService);
+
+    this._branch = this.prefService.getBranch("snav.");
+    this._branch.QueryInterface(Ci.nsIPrefBranch2);
+    this._branch.addObserver("", this, false);
+
+    // set current or default pref values
+    this.observe(null, "nsPref:changed", "enabled");
+    this.observe(null, "nsPref:changed", "xulContentEnabled");
+    this.observe(null, "nsPref:changed", "keyCode.modifier");
+    this.observe(null, "nsPref:changed", "keyCode.right");
+    this.observe(null, "nsPref:changed", "keyCode.up");
+    this.observe(null, "nsPref:changed", "keyCode.down");
+    this.observe(null, "nsPref:changed", "keyCode.left");
+    this.observe(null, "nsPref:changed", "keyCode.return");
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "nsPref:changed") {
+      return;
+    }
+
+    // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
+    // aData is the name of the pref that's been changed (relative to aSubject)
+    switch (aData) {
+      case "enabled":
+        try {
+          this.enabled = this._branch.getBoolPref("enabled");
+        } catch(e) {
+          this.enabled = false;
+        }
+        break;
+
+      case "xulContentEnabled":
+        try {
+          this.xulContentEnabled = this._branch.getBoolPref("xulContentEnabled");
+        } catch(e) {
+          this.xulContentEnabled = false;
+        }
+        break;
+
+      case "keyCode.modifier": {
+        let keyCodeModifier;
+        try {
+          keyCodeModifier = this._branch.getCharPref("keyCode.modifier");
+
+          // resetting modifiers
+          this.modifierAlt = false;
+          this.modifierShift = false;
+          this.modifierCtrl = false;
+
+          if (keyCodeModifier != this.kNone) {
+            // we are using '+' as a separator in about:config.
+            let mods = keyCodeModifier.split(/\++/);
+            for (let i = 0; i < mods.length; i++) {
+              let mod = mods[i].toLowerCase();
+              if (mod === "")
+                continue;
+              else if (mod == kAlt)
+                this.modifierAlt = true;
+              else if (mod == kShift)
+                this.modifierShift = true;
+              else if (mod == kCtrl)
+                this.modifierCtrl = true;
+              else {
+                keyCodeModifier = kNone;
+                break;
+              }
+            }
+          }
+        } catch(e) { }
+        break;
+      }
+
+      case "keyCode.up":
+        try {
+          this.keyCodeUp = this._branch.getIntPref("keyCode.up");
+        } catch(e) {
+          this.keyCodeUp = Ci.nsIDOMKeyEvent.DOM_VK_UP;
+        }
+        break;
+      case "keyCode.down":
+        try {
+          this.keyCodeDown = this._branch.getIntPref("keyCode.down");
+        } catch(e) {
+          this.keyCodeDown = Ci.nsIDOMKeyEvent.DOM_VK_DOWN;
+        }
+        break;
+      case "keyCode.left":
+        try {
+          this.keyCodeLeft = this._branch.getIntPref("keyCode.left");
+        } catch(e) {
+          this.keyCodeLeft = Ci.nsIDOMKeyEvent.DOM_VK_LEFT;
+        }
+        break;
+      case "keyCode.right":
+        try {
+          this.keyCodeRight = this._branch.getIntPref("keyCode.right");
+        } catch(e) {
+          this.keyCodeRight = Ci.nsIDOMKeyEvent.DOM_VK_RIGHT;
+        }
+        break;
+      case "keyCode.return":
+        try {
+          this.keyCodeReturn = this._branch.getIntPref("keyCode.return");
+        } catch(e) {
+          this.keyCodeReturn = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
+        }
+        break;
+    }
+  }
+};
+
+PrefObserver.register();
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -30,16 +30,17 @@ EXTRA_JS_MODULES += [
     'RemoteController.jsm',
     'RemoteFinder.jsm',
     'RemoteSecurityUI.jsm',
     'RemoteWebNavigation.jsm',
     'RemoteWebProgress.jsm',
     'SelectContentHelper.jsm',
     'SelectParentHelper.jsm',
     'Sntp.jsm',
+    'SpatialNavigation.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'TelemetryTimestamps.jsm',
     'Timer.jsm',
     'debug.js',
 ]
 
 EXTRA_PP_JS_MODULES += [
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/mochitest/mochitest.ini
@@ -0,0 +1,1 @@
+[test_spatial_navigation.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/mochitest/test_spatial_navigation.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=698437
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 698437</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 698437 **/
+
+  SimpleTest.waitForExplicitFinish();
+
+  function Test() {
+    if (!SpecialPowers.getBoolPref("snav.enabled")) {
+      todo(false, "Enable spatial navigiation on this platform.");
+      SimpleTest.finish();
+      return;
+    }
+
+    var center = document.getElementById("center");
+    var right = document.getElementById("right");
+    var left = document.getElementById("left");
+    var top = document.getElementById("top");
+    var bottom = document.getElementById("bottom");
+
+    console.log(top);
+    console.log(bottom);
+    console.log(center);
+    console.log(left);
+    console.log(right);
+
+    center.focus();
+    is(center.id, document.activeElement.id, "How did we call focus on center and it did" +
+                                             " not become the active element?");
+
+    synthesizeKey("VK_UP", { });
+    is(top.id, document.activeElement.id,
+       "Spatial navigation up key is not handled correctly.");
+
+    center.focus();
+    synthesizeKey("VK_DOWN", { });
+    is(bottom.id, document.activeElement.id,
+       "Spatial navigation down key is not handled correctly.");
+
+    center.focus();
+    synthesizeKey("VK_RIGHT", { });
+    is(right.id, document.activeElement.id,
+       "Spatial navigation right key is not handled correctly.");
+
+    center.focus();
+    synthesizeKey("VK_LEFT", { });
+    is(left.id, document.activeElement.id,
+       "Spatial navigation left key is not handled correctly.");
+
+    SimpleTest.finish();
+  }
+
+  </script>
+</head>
+<body onload="Test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=698437">Mozilla Bug 698437</a>
+<p id="display"></p>
+<div id="content">
+  <p> This is a <a id="top" href="#">really</a> long sentence </p>
+  <p> <a id="left" href="#">This</a> is a
+      <a id="center" href="#">really</a> long
+      <a id="right" href="#">sentence</a> </p>
+  <p> This is a <a id="bottom" href="#">really</a> long sentence </p>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/toolkit/modules/tests/moz.build
+++ b/toolkit/modules/tests/moz.build
@@ -4,8 +4,10 @@
 # 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/.
 
 DIRS += ['browser']
 
 MODULE = 'test_toolkit_general'
 
 XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
--- a/tools/profiler/platform-linux.cc
+++ b/tools/profiler/platform-linux.cc
@@ -461,20 +461,22 @@ static void StartSignalHandler(int signa
   // XXX: Everything we do here is NOT async signal safe. We risk nasty things
   // like deadlocks but we typically only do this once so it tends to be ok.
   // See bug 909403
   const char* threadName = NULL;
   uint32_t threadCount = 0;
   char thread[256];
 
   // TODO support selecting features from profiler.options
-  const char* features[2] = {NULL, NULL};
+  const char* features[3] = {NULL, NULL, NULL};
   uint32_t featureCount = 0;
   features[0] = "leaf";
   featureCount++;
+  features[1] = "js";
+  featureCount++;
   const char* threadFeature = "threads";
 
   std::ifstream infile;
   infile.open("/data/local/tmp/profiler.options");
   if (infile.is_open()) {
     infile.getline(thread, 256);
     threadName = thread;
     threadCount = 1;
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1433,16 +1433,19 @@ static unsigned int ConvertAndroidKeyCod
         case AKEYCODE_MUHENKAN:           return NS_VK_NONCONVERT;
         case AKEYCODE_HENKAN:             return NS_VK_CONVERT;
         case AKEYCODE_KATAKANA_HIRAGANA:  return 0;
         case AKEYCODE_YEN:                return NS_VK_BACK_SLASH; // Same as other platforms.
         case AKEYCODE_RO:                 return NS_VK_BACK_SLASH; // Same as other platforms.
         case AKEYCODE_KANA:               return NS_VK_KANA;
         case AKEYCODE_ASSIST:             return NS_VK_HELP;
 
+        // the A key is the action key for gamepad devices.
+        case AKEYCODE_BUTTON_A:          return NS_VK_RETURN;
+
         default:
             ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
                  "No DOM keycode for Android keycode %d", androidKeyCode);
         return 0;
     }
 }
 
 static KeyNameIndex ConvertAndroidKeyCodeToKeyNameIndex(int aAndroidKeyCode)