Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 16 Feb 2013 18:48:25 -0500
changeset 122148 c4de6de47382dc05c46be435fbead5fb85e73732
parent 122123 ff193fc3dc7d37db4081dd95f1f1aa8e3551d527 (current diff)
parent 122147 fd897af2dd4753fdb72ebe33cc0015c2f2a82265 (diff)
child 122157 86b0207c62d6f0baf4efb6f46e25dbcd92a2b254
push id24319
push userryanvm@gmail.com
push dateSat, 16 Feb 2013 23:49:46 +0000
treeherdermozilla-central@c4de6de47382 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
c4de6de47382 / 21.0a1 / 20130217031005 / files
nightly linux64
c4de6de47382 / 21.0a1 / 20130217031005 / files
nightly mac
c4de6de47382 / 21.0a1 / 20130217031005 / files
nightly win32
c4de6de47382 / 21.0a1 / 20130217031005 / files
nightly win64
c4de6de47382 / 21.0a1 / 20130217031005 / 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 inbound to m-c.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1245,17 +1245,16 @@
             var uriIsNotAboutBlank = aURI && aURI != "about:blank";
 
             if (uriIsBlankPage)
               t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
             else
               t.setAttribute("label", aURI);
 
             t.setAttribute("crop", "end");
-            t.setAttribute("validate", "never");
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
@@ -2878,17 +2877,16 @@
           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
           this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
           this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
 
           var tab = this.firstChild;
           tab.setAttribute("label",
                            this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
           tab.setAttribute("crop", "end");
-          tab.setAttribute("validate", "never");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
           this.adjustTabstrip();
 
           Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
           window.addEventListener("resize", this, false);
           window.addEventListener("load", this, false);
 
           try {
@@ -4090,18 +4088,19 @@
                     class="tab-background-end"/>
         </xul:hbox>
         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                   class="tab-content" align="center">
           <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
                      class="tab-throbber"
                      role="presentation"
                      layer="true" />
-          <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
+          <xul:image xbl:inherits="src=image,fadein,pinned,selected"
                      class="tab-icon-image"
+                     validate="never"
                      role="presentation"/>
           <xul:label flex="1"
                      xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
                      class="tab-text tab-label"
                      role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected"
                              class="tab-close-button"/>
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -438,16 +438,17 @@ user_pref("browser.console.showInPanel",
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("browser.firstrun.show.localepicker", false);
 user_pref("browser.firstrun.show.uidiscovery", false);
 user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
 user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
 user_pref("dom.allow_scripts_to_close_windows", true);
 user_pref("dom.disable_open_during_load", false);
 user_pref("dom.experimental_forms", true); // on for testing
+user_pref("dom.experimental_forms_range", true); // on for testing
 user_pref("dom.max_script_run_time", 0); // no slow script dialogs
 user_pref("hangmonitor.timeout", 0); // no hang monitor
 user_pref("dom.max_chrome_script_run_time", 0);
 user_pref("dom.popup_maximum", -1);
 user_pref("dom.send_after_paint_to_content", true);
 user_pref("dom.successive_dialog_time_limit", 0);
 user_pref("signed.applets.codebase_principal_support", true);
 user_pref("browser.shell.checkDefaultBrowser", false);
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -448,17 +448,20 @@ NS_INTERFACE_MAP_END
 
 // Threadsafe when GetMutable() == false
 NS_IMPL_THREADSAFE_ADDREF(nsDOMFile)
 NS_IMPL_THREADSAFE_RELEASE(nsDOMFile)
 
 ////////////////////////////////////////////////////////////////////////////
 // nsDOMFileCC implementation
 
-NS_IMPL_CYCLE_COLLECTION_0(nsDOMFileCC)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsDOMFileCC)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMFileCC)
+  // We don't have anything to traverse, but some of our subclasses do.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMFileCC)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFile)
   NS_INTERFACE_MAP_ENTRY(nsIDOMBlob)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMFile, mIsFile)
   NS_INTERFACE_MAP_ENTRY(nsIXHRSendable)
   NS_INTERFACE_MAP_ENTRY(nsIMutable)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(File, mIsFile)
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1793,16 +1793,17 @@ GK_ATOM(menuFrame,"MenuFrame")
 GK_ATOM(menuPopupFrame,"MenuPopupFrame")
 GK_ATOM(objectFrame, "ObjectFrame")
 GK_ATOM(pageFrame, "PageFrame")
 GK_ATOM(pageBreakFrame, "PageBreakFrame")
 GK_ATOM(pageContentFrame, "PageContentFrame")
 GK_ATOM(placeholderFrame, "PlaceholderFrame")
 GK_ATOM(popupSetFrame, "PopupSetFrame")
 GK_ATOM(canvasFrame, "CanvasFrame")
+GK_ATOM(rangeFrame, "RangeFrame")
 GK_ATOM(rootFrame, "RootFrame")
 GK_ATOM(scrollFrame, "ScrollFrame")
 GK_ATOM(scrollbarFrame, "ScrollbarFrame")
 GK_ATOM(sequenceFrame, "SequenceFrame")
 GK_ATOM(sliderFrame, "sliderFrame")
 GK_ATOM(tableCaptionFrame, "TableCaptionFrame")
 GK_ATOM(tableCellFrame, "TableCellFrame")
 GK_ATOM(tableColFrame, "TableColFrame")
new file mode 100644
--- /dev/null
+++ b/content/html/content/crashtests/838256-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<style>
+
+::-moz-range-track {
+  display: none ! important;
+}
+
+::-moz-range-thumb {
+  display: none ! important;
+}
+
+</style>
+</head>
+<body>
+<input type="range">
+</body>
+</html>
--- a/content/html/content/crashtests/crashtests.list
+++ b/content/html/content/crashtests/crashtests.list
@@ -44,8 +44,10 @@ load 795221-4.html
 load 795221-5.xml
 load 798802-1.html
 load 811226.html
 load 819745.html
 pref(dom.experimental_forms,true) load 828472.html
 load 828180.html
 load 832011.html
 load 837033.html
+pref(dom.experimental_forms_range,true) load 838256-1.html
+
--- a/content/html/content/public/nsIFormControl.h
+++ b/content/html/content/public/nsIFormControl.h
@@ -58,16 +58,17 @@ enum InputElementTypes {
   NS_FORM_INPUT_PASSWORD,
   NS_FORM_INPUT_RADIO,
   NS_FORM_INPUT_SEARCH,
   NS_FORM_INPUT_SUBMIT,
   NS_FORM_INPUT_TEL,
   NS_FORM_INPUT_TEXT,
   NS_FORM_INPUT_TIME,
   NS_FORM_INPUT_URL,
+  NS_FORM_INPUT_RANGE,
   eInputElementTypesMax
 };
 
 PR_STATIC_ASSERT((uint32_t)eFormControlsWithoutSubTypesMax < (uint32_t)NS_FORM_BUTTON_ELEMENT);
 PR_STATIC_ASSERT((uint32_t)eButtonElementTypesMax < (uint32_t)NS_FORM_INPUT_ELEMENT);
 PR_STATIC_ASSERT((uint32_t)eInputElementTypesMax  < 1<<8);
 
 #define NS_IFORMCONTROL_IID   \
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -179,16 +179,17 @@ ShouldBeInElements(nsIFormControl* aForm
   case NS_FORM_INPUT_PASSWORD :
   case NS_FORM_INPUT_RADIO :
   case NS_FORM_INPUT_SEARCH :
   case NS_FORM_INPUT_SUBMIT :
   case NS_FORM_INPUT_TEXT :
   case NS_FORM_INPUT_TEL :
   case NS_FORM_INPUT_URL :
   case NS_FORM_INPUT_NUMBER :
+  case NS_FORM_INPUT_RANGE :
   case NS_FORM_INPUT_DATE :
   case NS_FORM_INPUT_TIME :
   case NS_FORM_SELECT :
   case NS_FORM_TEXTAREA :
   case NS_FORM_FIELDSET :
   case NS_FORM_OBJECT :
   case NS_FORM_OUTPUT :
     return true;
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -125,27 +125,28 @@ static const nsAttrValue::EnumTable kInp
   { "email", NS_FORM_INPUT_EMAIL },
   { "file", NS_FORM_INPUT_FILE },
   { "hidden", NS_FORM_INPUT_HIDDEN },
   { "reset", NS_FORM_INPUT_RESET },
   { "image", NS_FORM_INPUT_IMAGE },
   { "number", NS_FORM_INPUT_NUMBER },
   { "password", NS_FORM_INPUT_PASSWORD },
   { "radio", NS_FORM_INPUT_RADIO },
+  { "range", NS_FORM_INPUT_RANGE },
   { "search", NS_FORM_INPUT_SEARCH },
   { "submit", NS_FORM_INPUT_SUBMIT },
   { "tel", NS_FORM_INPUT_TEL },
   { "text", NS_FORM_INPUT_TEXT },
   { "time", NS_FORM_INPUT_TIME },
   { "url", NS_FORM_INPUT_URL },
   { 0 }
 };
 
 // Default type is 'text'.
-static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[14];
+static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[15];
 
 static const uint8_t NS_INPUT_AUTOCOMPLETE_OFF     = 0;
 static const uint8_t NS_INPUT_AUTOCOMPLETE_ON      = 1;
 static const uint8_t NS_INPUT_AUTOCOMPLETE_DEFAULT = 2;
 
 static const nsAttrValue::EnumTable kInputAutocompleteTable[] = {
   { "", NS_INPUT_AUTOCOMPLETE_DEFAULT },
   { "on", NS_INPUT_AUTOCOMPLETE_ON },
@@ -174,17 +175,17 @@ static const nsAttrValue::EnumTable kInp
   { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
   { 0 }
 };
 
 // Default inputmode value is "auto".
 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
 
 const double nsHTMLInputElement::kStepScaleFactorDate = 86400000;
-const double nsHTMLInputElement::kStepScaleFactorNumber = 1;
+const double nsHTMLInputElement::kStepScaleFactorNumberRange = 1;
 const double nsHTMLInputElement::kStepScaleFactorTime = 1000;
 const double nsHTMLInputElement::kDefaultStepBase = 0;
 const double nsHTMLInputElement::kDefaultStep = 1;
 const double nsHTMLInputElement::kDefaultStepTime = 60;
 const double nsHTMLInputElement::kStepAny = 0;
 
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
@@ -706,16 +707,17 @@ nsHTMLInputElement::Clone(nsINodeInfo *a
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
+    case NS_FORM_INPUT_RANGE:
       if (mValueChanged) {
         // We don't have our default value anymore.  Set our value on
         // the clone.
         nsAutoString value;
         GetValueInternal(value);
         // SetValueInternal handles setting the VALUE_CHANGED bit for us
         it->SetValueInternal(value, false, true);
       }
@@ -876,22 +878,56 @@ nsHTMLInputElement::AfterSetAttr(int32_t
       UpdateTooLongValidityState();
     } else if (aName == nsGkAtoms::pattern) {
       UpdatePatternMismatchValidityState();
     } else if (aName == nsGkAtoms::multiple) {
       UpdateTypeMismatchValidityState();
     } else if (aName == nsGkAtoms::max) {
       UpdateHasRange();
       UpdateRangeOverflowValidityState();
+      if (mType == NS_FORM_INPUT_RANGE) {
+        // The value may need to change when @max changes since the value may
+        // have been invalid and can now change to a valid value, or vice
+        // versa. For example, consider:
+        // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
+        // while the nearest valid steps are -1 and 2 (the max value having
+        // prevented there being a valid step in range). Changing @max to/from
+        // 1 and a number greater than on equal to 3 should change whether we
+        // have a step mismatch or not.
+        // The value may also need to change between a value that results in
+        // a step mismatch and a value that results in overflow. For example,
+        // if @max in the example above were to change from 1 to -1.
+        nsAutoString value;
+        GetValue(value);
+        SetValueInternal(value, false, false);
+        MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+                   "HTML5 spec does not allow this");
+      }
     } else if (aName == nsGkAtoms::min) {
       UpdateHasRange();
       UpdateRangeUnderflowValidityState();
       UpdateStepMismatchValidityState();
+      if (mType == NS_FORM_INPUT_RANGE) {
+        // See @max comment
+        nsAutoString value;
+        GetValue(value);
+        SetValueInternal(value, false, false);
+        MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+                   "HTML5 spec does not allow this");
+      }
     } else if (aName == nsGkAtoms::step) {
       UpdateStepMismatchValidityState();
+      if (mType == NS_FORM_INPUT_RANGE) {
+        // See @max comment
+        nsAutoString value;
+        GetValue(value);
+        SetValueInternal(value, false, false);
+        MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+                   "HTML5 spec does not allow this");
+      }
     } else if (aName == nsGkAtoms::dir &&
                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
       SetDirectionIfAuto(true, aNotify);
     }
 
     UpdateState(aNotify);
   }
 
@@ -1078,16 +1114,17 @@ bool
 nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue,
                                           double& aResultValue) const
 {
   MOZ_ASSERT(DoesValueAsNumberApply(),
              "ConvertStringToNumber only applies if .valueAsNumber applies");
 
   switch (mType) {
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_RANGE:
       {
         nsresult ec;
         aResultValue = PromiseFlatString(aValue).ToDouble(&ec);
         if (NS_FAILED(ec) || !MOZ_DOUBLE_IS_FINITE(aResultValue)) {
           return false;
         }
 
         return true;
@@ -1251,16 +1288,17 @@ nsHTMLInputElement::ConvertNumberToStrin
              "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
   MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(aValue) && !MOZ_DOUBLE_IS_INFINITE(aValue),
              "aValue must be a valid non-Infinite number.");
 
   aResultString.Truncate();
 
   switch (mType) {
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_RANGE:
       aResultString.AppendFloat(aValue);
       return true;
     case NS_FORM_INPUT_DATE:
       {
         SafeAutoJSContext ctx;
         JSAutoRequest ar(ctx);
 
         // The specs require |aValue| to be truncated.
@@ -1451,56 +1489,59 @@ nsHTMLInputElement::SetValueAsNumber(dou
 }
 
 double
 nsHTMLInputElement::GetMinimum() const
 {
   MOZ_ASSERT(DoesValueAsNumberApply(),
              "GetMinAsDouble() should only be used for types that allow .valueAsNumber");
 
-  // Once we add support for types that have a default minimum/maximum, take
-  // account of the default minimum here.
+  // Only type=range has a default minimum
+  double defaultMinimum =
+    mType == NS_FORM_INPUT_RANGE ? 0.0 : MOZ_DOUBLE_NaN();
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
-    return MOZ_DOUBLE_NaN();
+    return defaultMinimum;
   }
 
   nsAutoString minStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
 
   double min;
-  return ConvertStringToNumber(minStr, min) ? min : MOZ_DOUBLE_NaN();
+  return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
 }
 
 double
 nsHTMLInputElement::GetMaximum() const
 {
   MOZ_ASSERT(DoesValueAsNumberApply(),
              "GetMaxAsDouble() should only be used for types that allow .valueAsNumber");
 
-  // Once we add support for types that have a default minimum/maximum, take
-  // account of the default maximum here.
+  // Only type=range has a default maximum
+  double defaultMaximum =
+    mType == NS_FORM_INPUT_RANGE ? 100.0 : MOZ_DOUBLE_NaN();
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
-    return MOZ_DOUBLE_NaN();
+    return defaultMaximum;
   }
 
   nsAutoString maxStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
   double max;
-  return ConvertStringToNumber(maxStr, max) ? max : MOZ_DOUBLE_NaN();
+  return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
 }
 
 double
 nsHTMLInputElement::GetStepBase() const
 {
   MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
              mType == NS_FORM_INPUT_DATE ||
-             mType == NS_FORM_INPUT_TIME,
+             mType == NS_FORM_INPUT_TIME ||
+             mType == NS_FORM_INPUT_RANGE,
              "Check that kDefaultStepBase is correct for this new type");
 
   double stepBase;
 
   // Do NOT use GetMinimum here - the spec says to use "the min content
   // attribute", not "the minimum".
   nsAutoString minStr;
   if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
@@ -3075,16 +3116,83 @@ nsHTMLInputElement::SanitizeValue(nsAStr
       {
         nsresult ec;
         double val = PromiseFlatString(aValue).ToDouble(&ec);
         if (NS_FAILED(ec) || !MOZ_DOUBLE_IS_FINITE(val)) {
           aValue.Truncate();
         }
       }
       break;
+    case NS_FORM_INPUT_RANGE:
+      {
+        double minimum = GetMinimum();
+        double maximum = GetMaximum();
+        MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) &&
+                   MOZ_DOUBLE_IS_FINITE(maximum),
+                   "type=range should have a default maximum/minimum");
+
+        // We use this to avoid modifying the string unnecessarily, since that
+        // may introduce rounding. This is set to true only if the value we
+        // parse out from aValue needs to be sanitized.
+        bool needSanitization = false;
+
+        double value;
+        bool ok = ConvertStringToNumber(aValue, value);
+        if (!ok) {
+          needSanitization = true;
+          // Set value to midway between minimum and maximum.
+          value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/2.0;
+        } else if (value < minimum || maximum < minimum) {
+          needSanitization = true;
+          value = minimum;
+        } else if (value > maximum) {
+          needSanitization = true;
+          value = maximum;
+        }
+
+        double step = GetStep();
+        if (step != kStepAny) {
+          double stepBase = GetStepBase();
+          // There could be rounding issues below when dealing with fractional
+          // numbers, but let's ignore that until ECMAScript supplies us with a
+          // decimal number type.
+          double deltaToStep = NS_floorModulo(value - stepBase, step);
+          if (deltaToStep != 0) {
+            // "suffering from a step mismatch"
+            // Round the element's value to the nearest number for which the
+            // element would not suffer from a step mismatch, and which is
+            // greater than or equal to the minimum, and, if the maximum is not
+            // less than the minimum, which is less than or equal to the
+            // maximum, if there is a number that matches these constraints:
+            MOZ_ASSERT(deltaToStep > 0, "stepBelow/stepAbove will be wrong");
+            double stepBelow = value - deltaToStep;
+            double stepAbove = value - deltaToStep + step;
+            double halfStep = step / 2;
+            bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
+            bool stepAboveInRange = stepAbove >= minimum &&
+                                    stepAbove <= maximum;
+            bool stepBelowInRange = stepBelow >= minimum &&
+                                    stepBelow <= maximum;
+
+            if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
+              needSanitization = true;
+              value = stepAbove;
+            } else if ((!stepAboveIsClosest || !stepAboveInRange) && stepBelowInRange) {
+              needSanitization = true;
+              value = stepBelow;
+            }
+          }
+        }
+
+        if (needSanitization) {
+          aValue.Truncate();
+          aValue.AppendFloat(value);
+        }
+      }
+      break;
     case NS_FORM_INPUT_DATE:
       {
         if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
           aValue.Truncate();
         }
       }
       break;
     case NS_FORM_INPUT_TIME:
@@ -3294,18 +3402,20 @@ nsHTMLInputElement::ParseAttribute(int32
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::type) {
       // XXX ARG!! This is major evilness. ParseAttribute
       // shouldn't set members. Override SetAttr instead
       int32_t newType;
       bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
       if (success) {
         newType = aResult.GetEnumValue();
-        if (IsExperimentalMobileType(newType) &&
-            !Preferences::GetBool("dom.experimental_forms", false)) {
+        if ((IsExperimentalMobileType(newType) &&
+             !Preferences::GetBool("dom.experimental_forms", false)) ||
+            (newType == NS_FORM_INPUT_RANGE &&
+             !Preferences::GetBool("dom.experimental_forms_range", false))) {
           newType = kInputDefaultType->value;
           aResult.SetTo(newType, &aValue);
         }
         if (newType == NS_FORM_INPUT_FILE &&
             Preferences::GetBool("dom.disable_input_file", false)) {
           newType = kInputDefaultType->value;
           aResult.SetTo(newType, &aValue);
         }
@@ -3927,16 +4037,17 @@ nsHTMLInputElement::SaveState()
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_HIDDEN:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
+    case NS_FORM_INPUT_RANGE:
       {
         if (mValueChanged) {
           inputState = new nsHTMLInputElementState();
           nsAutoString value;
           GetValue(value);
           DebugOnly<nsresult> rv =
             nsLinebreakConverter::ConvertStringLineBreaks(
                  value,
@@ -4113,16 +4224,17 @@ nsHTMLInputElement::RestoreState(nsPresS
       case NS_FORM_INPUT_SEARCH:
       case NS_FORM_INPUT_TEXT:
       case NS_FORM_INPUT_TEL:
       case NS_FORM_INPUT_URL:
       case NS_FORM_INPUT_HIDDEN:
       case NS_FORM_INPUT_NUMBER:
       case NS_FORM_INPUT_DATE:
       case NS_FORM_INPUT_TIME:
+      case NS_FORM_INPUT_RANGE:
         {
           SetValueInternal(inputState->GetValue(), false, true);
           break;
         }
       case NS_FORM_INPUT_FILE:
         {
           const nsCOMArray<nsIDOMFile>& files = inputState->GetFiles();
           SetFiles(files, true);
@@ -4337,16 +4449,17 @@ nsHTMLInputElement::GetValueMode() const
 #ifdef DEBUG
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_RANGE:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
       return VALUE_MODE_VALUE;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
       return VALUE_MODE_VALUE;
 #else // DEBUG
     default:
@@ -4371,19 +4484,19 @@ nsHTMLInputElement::DoesReadOnlyApply() 
     case NS_FORM_INPUT_HIDDEN:
     case NS_FORM_INPUT_BUTTON:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_RADIO:
     case NS_FORM_INPUT_FILE:
     case NS_FORM_INPUT_CHECKBOX:
+    case NS_FORM_INPUT_RANGE:
     // TODO:
     // case NS_FORM_INPUT_COLOR:
-    // case NS_FORM_INPUT_RANGE:
       return false;
 #ifdef DEBUG
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
@@ -4406,19 +4519,19 @@ nsHTMLInputElement::DoesRequiredApply() 
 {
   switch (mType)
   {
     case NS_FORM_INPUT_HIDDEN:
     case NS_FORM_INPUT_BUTTON:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
+    case NS_FORM_INPUT_RANGE:
     // TODO:
     // case NS_FORM_INPUT_COLOR:
-    // case NS_FORM_INPUT_RANGE:
       return false;
 #ifdef DEBUG
     case NS_FORM_INPUT_RADIO:
     case NS_FORM_INPUT_CHECKBOX:
     case NS_FORM_INPUT_FILE:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
@@ -4464,18 +4577,18 @@ nsHTMLInputElement::DoesPatternApply() c
 bool
 nsHTMLInputElement::DoesMinMaxApply() const
 {
   switch (mType)
   {
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
+    case NS_FORM_INPUT_RANGE:
     // TODO:
-    // case NS_FORM_INPUT_RANGE:
     // All date/time types.
       return true;
 #ifdef DEBUG
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_BUTTON:
     case NS_FORM_INPUT_HIDDEN:
@@ -4942,17 +5055,18 @@ nsHTMLInputElement::GetValidationMessage
       aValidationMessage = message;
       break;
     }
     case VALIDITY_STATE_RANGE_OVERFLOW:
     {
       nsXPIDLString message;
 
       nsAutoString maxStr;
-      if (mType == NS_FORM_INPUT_NUMBER) {
+      if (mType == NS_FORM_INPUT_NUMBER ||
+          mType == NS_FORM_INPUT_RANGE) {
         //We want to show the value as parsed when it's a number
         double maximum = GetMaximum();
         MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(maximum));
 
         maxStr.AppendFloat(maximum);
       } else if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_TIME) {
         GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
       } else {
@@ -4966,17 +5080,18 @@ nsHTMLInputElement::GetValidationMessage
       aValidationMessage = message;
       break;
     }
     case VALIDITY_STATE_RANGE_UNDERFLOW:
     {
       nsXPIDLString message;
 
       nsAutoString minStr;
-      if (mType == NS_FORM_INPUT_NUMBER) {
+      if (mType == NS_FORM_INPUT_NUMBER ||
+          mType == NS_FORM_INPUT_RANGE) {
         double minimum = GetMinimum();
         MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(minimum));
 
         minStr.AppendFloat(minimum);
       } else if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_TIME) {
         GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
       } else {
         NS_NOTREACHED("Unexpected input type");
@@ -5441,33 +5556,35 @@ double
 nsHTMLInputElement::GetStepScaleFactor() const
 {
   MOZ_ASSERT(DoesStepApply());
 
   switch (mType) {
     case NS_FORM_INPUT_DATE:
       return kStepScaleFactorDate;
     case NS_FORM_INPUT_NUMBER:
-      return kStepScaleFactorNumber;
+    case NS_FORM_INPUT_RANGE:
+      return kStepScaleFactorNumberRange;
     case NS_FORM_INPUT_TIME:
       return kStepScaleFactorTime;
     default:
       MOZ_NOT_REACHED();
       return MOZ_DOUBLE_NaN();
   }
 }
 
 double
 nsHTMLInputElement::GetDefaultStep() const
 {
   MOZ_ASSERT(DoesStepApply());
 
   switch (mType) {
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_RANGE:
       return kDefaultStep;
     case NS_FORM_INPUT_TIME:
       return kDefaultStepTime;
     default:
       MOZ_NOT_REACHED();
       return MOZ_DOUBLE_NaN();
   }
 }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -278,16 +278,44 @@ public:
   bool Indeterminate() const { return mIndeterminate; }
   bool Checked() const { return mChecked; }
 
   /**
    * Fires change event if mFocusedValue and current value held are unequal.
    */
   void FireChangeEventIfNeeded();
 
+  /**
+   * Returns the input element's value as a double-precision float.
+   * Returns NaN if the current element's value is not a floating point number.
+   *
+   * @return the input element's value as a double-precision float.
+   */
+  double GetValueAsDouble() const;
+
+  /**
+   * Returns the input's "minimum" (as defined by the HTML5 spec) as a double.
+   * Note this takes account of any default minimum that the type may have.
+   * Returns NaN if the min attribute isn't a valid floating point number and
+   * the input's type does not have a default minimum.
+   *
+   * NOTE: Only call this if you know DoesMinMaxApply() returns true.
+   */
+  double GetMinimum() const;
+
+  /**
+   * Returns the input's "maximum" (as defined by the HTML5 spec) as a double.
+   * Note this takes account of any default maximum that the type may have.
+   * Returns NaN if the max attribute isn't a valid floating point number and
+   * the input's type does not have a default maximum.
+   *
+   * NOTE:Only call this if you know DoesMinMaxApply() returns true.
+   */
+  double GetMaximum() const;
+
 protected:
   // Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden
   // by the nsITextControlElement version.
   using nsGenericHTMLFormElement::IsSingleLineTextControl;
 
   /**
    * The ValueModeType specifies how the value IDL attribute should behave.
    *
@@ -565,24 +593,16 @@ protected:
    * Returns the radio group container if the element has one, null otherwise.
    * The radio group container will be the form owner if there is one.
    * The current document otherwise.
    * @return the radio group container if the element has one, null otherwise.
    */
   nsIRadioGroupContainer* GetRadioGroupContainer() const;
 
   /**
-   * Returns the input element's value as a double-precision float.
-   * Returns NaN if the current element's value is not a floating point number.
-   *
-   * @return the input element's value as a double-precision float.
-   */
-  double GetValueAsDouble() const;
-
-  /**
    * Convert a string to a number in a type specific way,
    * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#concept-input-value-string-number
    * ie parse a date string to a timestamp if type=date,
    * or parse a number string to its value if type=number.
    * @param aValue the string to be parsed.
    * @param aResultValue the timestamp as a double.
    * @result whether the parsing was successful.
    */
@@ -654,38 +674,16 @@ protected:
    */
   void SetValue(double aValue);
 
   /**
    * Update the HAS_RANGE bit field value.
    */
   void UpdateHasRange();
 
-  /**
-   * Returns the input's "minimum" (as defined by the HTML5 spec) as a double.
-   * Note this takes account of any default minimum that the type may have.
-   * Returns NaN if the min attribute isn't a valid floating point number and
-   * the input's type does not have a default minimum. Otherwise, guaranteed
-   * to return a finite value.
-   *
-   * NOTE: Only call this if you know DoesMinMaxApply() returns true.
-   */
-  double GetMinimum() const;
-
-  /**
-   * Returns the input's "maximum" (as defined by the HTML5 spec) as a double.
-   * Note this takes account of any default maximum that the type may have.
-   * Returns NaN if the max attribute isn't a valid floating point number and
-   * the input's type does not have a default maximum. Otherwise, guaranteed
-   * to return a finite value.
-   *
-   * NOTE:Only call this if you know DoesMinMaxApply() returns true.
-   */
-  double GetMaximum() const;
-
    /**
     * Get the step scale value for the current type.
     * See:
     * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
     */
   double GetStepScaleFactor() const;
 
   /**
@@ -769,17 +767,17 @@ protected:
    * a change event. This is to ensure correct future change event firing.
    * NB: This is ONLY applicable where the element is a text control. ie,
    * where type= "text", "email", "search", "tel", "url" or "password".
    */
   nsString mFocusedValue;  
 
   // Step scale factor values, for input types that have one.
   static const double kStepScaleFactorDate;
-  static const double kStepScaleFactorNumber;
+  static const double kStepScaleFactorNumberRange;
   static const double kStepScaleFactorTime;
 
   // Default step base value when a type do not have specific one.
   static const double kDefaultStepBase;
 
   // Default step used when there is no specified step.
   static const double kDefaultStep;
   static const double kDefaultStepTime;
--- a/content/html/content/test/forms/test_input_attributes_reflection.html
+++ b/content/html/content/test/forms/test_input_attributes_reflection.html
@@ -199,21 +199,21 @@ reflectString({
 });
 
 // .type
 reflectLimitedEnumerated({
   element: document.createElement("input"),
   attribute: "type",
   validValues: [ "hidden", "text", "search", "tel", "url", "email", "password",
                  "checkbox", "radio", "file", "submit", "image", "reset",
-                 "button", "date", "time", "number" ],
+                 "button", "date", "time", "number", "range" ],
   invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
   defaultValue: "text",
   unsupportedValues: [ "datetime", "month", "week", "datetime-local",
-                       "range", "color" ]
+                       "color" ]
 });
 
 // .defaultValue
 reflectString({
   element: document.createElement("input"),
   attribute: { idl: "defaultValue", content: "value" },
   otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
 });
--- a/content/html/content/test/forms/test_input_sanitization.html
+++ b/content/html/content/test/forms/test_input_sanitization.html
@@ -13,30 +13,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <pre id="test">
 <div id='content'>
   <form>
   </form>
 </div>
 <script type="application/javascript">
 
-/** Test for Bug 549475 **/
+/**
+ * This files tests the 'value sanitization algorithm' for the various input
+ * types. Note that an input's value is affected by more than just its type's
+ * value sanitization algorithm; e.g. some type=range has actions that the user
+ * agent must perform to change the element's value to avoid underflow/overflow
+ * and step mismatch (when possible). We specifically avoid triggering these
+ * other actions here so that this test only tests the value sanitization
+ * algorithm for the various input types.
+ *
+ * XXXjwatt splitting out testing of the value sanitization algorithm and 
+ * "other things" that affect .value makes it harder to know what we're testing
+ * and what we've missed, because what's included in the value sanitization
+ * algorithm and what's not is different from input type to input type. It
+ * seems to me it would be better to have a test (maybe one per type) focused
+ * on testing .value for permutations of all other inputs that can affect it.
+ * The value sanitization algorithm is just an internal spec concept after all.
+ */
 
 // We are excluding "file" because it's too different from the other types.
 // And it has no sanitizing algorithm.
 var inputTypes =
 [
   "text", "password", "search", "tel", "hidden", "checkbox", "radio",
   "submit", "image", "reset", "button", "email", "url", "number", "date",
-  "time",
+  "time", "range"
 ];
 
 var todoTypes =
 [
-  "range", "color",
+  "color",
   "month", "week", "datetime", "datetime-local",
 ];
 
 var valueModeValue =
 [
   "text", "search", "url", "tel", "email", "password", "date", "datetime",
   "month", "week", "time", "datetime-local", "number", "range", "color",
 ];
@@ -50,16 +66,30 @@ function sanitizeValue(aType, aValue)
     case "search":
     case "tel":
       return aValue.replace(/[\n\r]/g, "");
     case "url":
     case "email":
       return aValue.replace(/[\n\r]/g, "").replace(/^[\u0020\u0009\t\u000a\u000c\u000d]+|[\u0020\u0009\t\u000a\u000c\u000d]+$/g, "");
     case "number":
       return isNaN(Number(aValue)) ? "" : aValue;
+    case "range":
+      var defaultMinimum = 0;
+      var defaultMaximum = 100;
+      var value = Number(aValue);
+      if (isNaN(value)) {
+        return ((defaultMaximum - defaultMinimum)/2).toString(); // "50"
+      }
+      if (value < defaultMinimum) {
+        return defaultMinimum.toString();
+      }
+      if (value > defaultMaximum) {
+        return defaultMaximum.toString();
+      }
+      return aValue;
     case "date":
       // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
       function getNumbersOfDaysInMonth(aMonth, aYear) {
         if (aMonth === 2) {
           return (aYear % 400 === 0 || (aYear % 100 != 0 && aYear % 4 === 0)) ? 29 : 28;
         }
         return (aMonth === 1 || aMonth === 3 || aMonth === 5 || aMonth === 7 ||
                 aMonth === 8 || aMonth === 10 || aMonth === 12) ? 31 : 30;
@@ -116,20 +146,16 @@ function sanitizeValue(aType, aValue)
       return aValue;
     case "month":
     case "week":
     case "datetime":
     case "datetime-local":
       // TODO: write the sanitize algorithm.
       ok(false);
       return "";
-    case "range":
-      ok(false);
-      // TODO: write the sanitize algorithm.
-      return "";
     case "color":
       ok(false);
       // TODO: write the sanitize algorithm.
       return "";
     default:
       return aValue;
   }
 }
@@ -146,17 +172,17 @@ function checkSanitizing(element)
     // For url:
     "\r\n foobar    \n\r",
     "\u000B foo \u000B",
     "\u000A foo \u000A",
     "\u000C foo \u000C",
     "\u000d foo \u000d",
     "\u0020 foo \u0020",
     " \u0009 foo \u0009 ",
-    // For number:
+    // For number and range:
     "42",
     "13.37",
     "1.234567898765432",
     "12foo",
     "1e2",
     "3E42",
     // For date:
     "1970-01-01",
--- a/content/html/content/test/forms/test_max_attribute.html
+++ b/content/html/content/test/forms/test_max_attribute.html
@@ -29,30 +29,43 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
 ];
 
 var input = document.createElement("input");
 document.getElementById('content').appendChild(input);
 
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ *   (aElement.validity.valid is true) or not. The value passed is ignored and
+ *   overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ *   element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ *   "[candidate] for constraint validation" and it "[has] range limitations"
+ *   per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ *   (in other words, one of the pseudo classes :in-range and :out-of-range
+ *   should apply (which, depends on aValidity)).
+ *   Else (neither :in-range or :out-of-range should match) set to false.
+ */
 function checkValidity(aElement, aValidity, aApply, aRangeApply)
 {
   aValidity = aApply ? aValidity : true;
 
   is(aElement.validity.valid, aValidity,
      "element validity should be " + aValidity);
   is(aElement.validity.rangeOverflow, !aValidity,
      "element overflow status should be " + !aValidity);
@@ -81,17 +94,25 @@ for (var test of data) {
   input.type = test.type;
   var apply = test.apply;
 
   if (test.todo) {
     todo_is(input.type, test.type, test.type + " isn't implemented yet");
     continue;
   }
 
-  checkValidity(input, true, apply, false);
+  // The element should be valid. Range should not apply when @min and @max are
+  // undefined, except if the input type is 'range' (since that type has a
+  // default minimum and maximum).
+  if (input.type == 'range') {
+    checkValidity(input, true, apply, true);
+  } else {
+    checkValidity(input, true, apply, false);
+  }
+  checkValidity(input, true, apply, test.type == 'range');
 
   switch (input.type) {
     case 'hidden':
     case 'text':
     case 'search':
     case 'password':
     case 'url':
     case 'tel':
@@ -107,16 +128,24 @@ for (var test of data) {
       input.max = '-1';
       break;
     case 'date':
       input.max = '2012-06-27';
       break;
     case 'time':
       input.max = '02:20';
       break;
+    case 'range':
+      // range is special, since setting max to -1 will make it invalid since
+      // it's default would then be 0, meaning it suffers from overflow.
+      input.max = '-1';
+      checkValidity(input, false, apply, apply);
+      // Now make it something that won't cause an error below:
+      input.max = '10';
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
     case 'text':
@@ -231,16 +260,56 @@ for (var test of data) {
         input.max = "4.333333333333333333333333333333333331";
         input.value = "5";
         is(input.validationMessage,
            "Please select a value that is lower than 4.33333333333333.",
            "validation message");
       }
 
       break;
+    case 'range':
+      input.max = '2';
+      input.value = '1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '2';
+      checkValidity(input, true, apply, apply);
+
+      input.value = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '3';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.max, "the value should have been set to max");
+
+      input.max = '5';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '42';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.max, "the value should have been set to max");
+
+      input.max = '';
+      checkValidity(input, true, apply, apply);
+
+      input.max = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      // Check that we correctly convert input.max to a double in validationMessage.
+      input.step = 'any';
+      input.min = 5;
+      input.max = 0.66666666666666666666666666666666666
+      input.value = 1;
+      is(input.validationMessage,
+         "Please select a value that is lower than 0.666666666666667.",
+         "validation message")
+
+      break;
     case 'time':
       // Don't worry about that.
       input.step = 'any';
 
       input.max = '10:10';
       input.value = '10:09';
       checkValidity(input, true, apply, apply);
 
--- a/content/html/content/test/forms/test_min_attribute.html
+++ b/content/html/content/test/forms/test_min_attribute.html
@@ -29,30 +29,43 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
 ];
 
 var input = document.createElement("input");
 document.getElementById('content').appendChild(input);
 
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ *   (aElement.validity.valid is true) or not. The value passed is ignored and
+ *   overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ *   element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ *   "[candidate] for constraint validation" and it "[has] range limitations"
+ *   per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ *   (in other words, one of the pseudo classes :in-range and :out-of-range
+ *   should apply (which, depends on aValidity)).
+ *   Else (neither :in-range or :out-of-range should match) set to false.
+ */
 function checkValidity(aElement, aValidity, aApply, aRangeApply)
 {
   aValidity = aApply ? aValidity : true;
 
   is(aElement.validity.valid, aValidity,
      "element validity should be " + aValidity);
   is(aElement.validity.rangeUnderflow, !aValidity,
      "element underflow status should be " + !aValidity);
@@ -81,18 +94,24 @@ for (var test of data) {
   input.type = test.type;
   var apply = test.apply;
 
   if (test.todo) {
     todo_is(input.type, test.type, test.type + " isn't implemented yet");
     continue;
   }
 
-  // The element should be valid. Range should not apply.
-  checkValidity(input, true, apply, false);
+  // The element should be valid. Range should not apply when @min and @max are
+  // undefined, except if the input type is 'range' (since that type has a
+  // default minimum and maximum).
+  if (input.type == 'range') {
+    checkValidity(input, true, apply, true);
+  } else {
+    checkValidity(input, true, apply, false);
+  }
 
   switch (input.type) {
     case 'hidden':
     case 'text':
     case 'search':
     case 'password':
     case 'url':
     case 'tel':
@@ -108,16 +127,21 @@ for (var test of data) {
       input.min = '999';
       break;
     case 'date':
       input.min = '2012-06-27';
       break;
     case 'time':
       input.min = '20:20';
       break;
+    case 'range':
+      // range is special, since setting min to 999 will make it invalid since
+      // it's default maximum is 100, its value would be 999, and it would
+      // suffer from overflow.
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   // The element should still be valid and range should apply if it can.
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
@@ -229,16 +253,52 @@ for (var test of data) {
       // Check that we correctly convert input.min to a double in
       // validationMessage.
       input.min = "4.333333333333333333333333333333333331";
       input.value = "2";
       is(input.validationMessage,
          "Please select a value that is higher than 4.33333333333333.",
          "validation message");
       break;
+    case 'range':
+      input.min = '0';
+      input.value = '1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '0';
+      checkValidity(input, true, apply, apply);
+
+      input.value = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '-1';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.min, "the value should have been set to min");
+
+      input.min = '-1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '-42';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.min, "the value should have been set to min");
+
+      input.min = '';
+      checkValidity(input, true, apply, true);
+
+      input.min = 'foo';
+      checkValidity(input, true, apply, true);
+
+      // We don't check the conversion of input.min to a double in
+      // validationMessage for 'range' since range will always clamp the value
+      // up to at least the minimum (so we will never see the min in a
+      // validationMessage).
+
+      break;
     case 'time':
       // Don't worry about that.
       input.step = 'any';
 
       input.min = '20:20';
       input.value = '20:20:01';
       checkValidity(input, true, apply, apply);
 
--- a/content/html/content/test/forms/test_mozistextfield.html
+++ b/content/html/content/test/forms/test_mozistextfield.html
@@ -43,27 +43,27 @@ var gInputTestData = [
   ['reset',    false],
   ['image',    false],
   ['radio',    false],
   ['submit',   false],
   ['search',   true],
   ['email',    true],
   ['url',      true],
   ['number',   false],
+  ['range',    false],
   ['date',     false],
   ['time',     false],
 ];
 
 /**
  * TODO: the next types are not yet in the tree.
  * The value is only a suggestion.
  */
 var gInputTodoData = [
 /* type        expected result */
-  ['range',    false],
   ['color',    false],
   ['datetime', false],
   ['month',    false],
   ['week',     false],
   ['datetime-local', false],
 ];
 
 function checkMozIsTextFieldDefined(aElement, aResult)
--- a/content/html/content/test/forms/test_pattern_attribute.html
+++ b/content/html/content/test/forms/test_pattern_attribute.html
@@ -256,18 +256,18 @@ function checkPatternValidity(element)
 }
 
 var input = document.getElementById('i');
 
 // |validTypes| are the types which accept @pattern
 // and |invalidTypes| are the ones which do not accept it.
 var validTypes = Array('text', 'password', 'search', 'tel', 'email', 'url');
 var barredTypes = Array('hidden', 'reset', 'button', 'submit', 'image');
-var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'date', 'time');
-// TODO: 'datetime', 'month', 'week', 'datetime-local', 'range' and 'color'
+var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'range', 'date', 'time');
+// TODO: 'datetime', 'month', 'week', 'datetime-local' and 'color'
 //       do not accept the @pattern too but are not implemented yet.
 
 for (type of validTypes) {
   input.type = type;
   completeValidityCheck(input, false);
   checkPatternValidity(input);
 }
 
--- a/content/html/content/test/forms/test_required_attribute.html
+++ b/content/html/content/test/forms/test_required_attribute.html
@@ -362,18 +362,18 @@ checkTextareaRequiredValidity();
 // First of all, checks for types that make the element barred from
 // constraint validation.
 var typeBarredFromConstraintValidation = ["hidden", "button", "reset", "submit", "image"];
 for (type of typeBarredFromConstraintValidation) {
   checkInputRequiredNotApply(type, true);
 }
 
 // Then, checks for the types which do not use the required attribute.
-// TODO: check 'color' and 'range' when they will be implemented.
-var typeRequireNotApply = [];
+// TODO: check 'color' when they will be implemented.
+var typeRequireNotApply = ['range'];
 for (type of typeRequireNotApply) {
   checkInputRequiredNotApply(type, false);
 }
 
 // Now, checking for all types which accept the required attribute.
 // TODO: check 'datetime', 'month', 'week' and 'datetime-local'
 //       when they will be implemented.
 var typeRequireApply = ["text", "password", "search", "tel", "email", "url",
--- a/content/html/content/test/forms/test_step_attribute.html
+++ b/content/html/content/test/forms/test_step_attribute.html
@@ -29,17 +29,17 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
@@ -412,16 +412,223 @@ for (var test of data) {
       input.min = '1';
       input.max = '10.9';
       input.value = '10';
 
       is(input.validationMessage, "Please select a valid value. " +
                                   "The nearest valid value is 9.",
          "The validation message should not include the higher value.");
       break;
+    case 'range':
+      // Range is special in that it clamps to valid values, so it is much
+      // rarer for it to be invalid.
+
+      // When step=0, the allowed value step is 1.
+      input.step = '0';
+      input.value = '1.2';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0';
+      is(input.value, 0, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is NaN, the allowed step value is 1.
+      input.step = 'foo';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // When step is negative, the allowed step value is 1.
+      input.step = '-0.1';
+      is(input.value, 2, "check that the value still coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is missing, the allowed step value is 1.
+      input.removeAttribute('step');
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is 'any', all values are fine wrt to step.
+      input.step = 'any';
+      checkValidity(input, true, apply);
+
+      input.step = 'aNy';
+      input.value = '97';
+      is(input.value, 97, "check that the value for step=aNy is unchanged");
+      checkValidity(input, true, apply);
+
+      input.step = 'AnY';
+      input.value = '0.1';
+      is(input.value, 0.1, "check that a positive fractional value with step=AnY is unchanged");
+      checkValidity(input, true, apply);
+
+      input.step = 'ANY';
+      input.min = -100;
+      input.value = '-13.37';
+      is(input.value, -13.37, "check that a negative fractional value with step=ANY is unchanged");
+      checkValidity(input, true, apply);
+
+      // When min is set to a valid float, there is a step base.
+      input.min = '1'; // the step base
+      input.step = '2';
+      input.value = '3';
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '2';
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1.99';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.removeAttribute('step'); // step = 1
+      input.min = '0.5'; // step base
+      input.value = '5.5';
+      is(input.value, 5.5, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1.5, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.min = '-0.1'; // step base
+      input.step = '1';
+      input.value = '0.9';
+      is(input.value, 0.9, "the value should be a valid step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.1';
+      is(input.value, -0.1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // When min is set to NaN, the step base is the value.
+      input.min = 'foo';
+      input.step = '1';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.5';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.min = '';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.5';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.removeAttribute('min');
+
+      // Test when the value isn't a number
+      input.value = '';
+      is(input.value, 50, "value be should default to the value midway between the minimum (0) and the maximum (100)");
+      checkValidity(input, true, apply);
+
+      // Regular situations.
+      input.step = '2';
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '42.0';
+      is(input.value, 42, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.step = '0.1';
+      input.value = '-0.1';
+      is(input.value, 0, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.step = '2';
+      input.removeAttribute('min');
+      input.max = '10';
+      input.value = '-9';
+      is(input.value, 0, "check the value is clamped to the minimum's default of zero");
+      checkValidity(input, true, apply);
+
+      // If @value is defined but not @min, the step base is @value.
+      input = getFreshElement(test.type);
+      input.setAttribute('value', '1');
+      input.step = 2;
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = 3;
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 2;
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // Should also work with defaultValue.
+      input = getFreshElement(test.type);
+      input.defaultValue = 1;
+      input.step = 2;
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 3;
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 2;
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // Check contrived error case where there are no valid steps in range:
+      // No @min, so the step base is the default minimum, zero, the valid
+      // range is 0-1, -1 gets clamped to zero.
+      input = getFreshElement(test.type);
+      input.step = '3';
+      input.max = '1';
+      input.defaultValue = '-1';
+      is(input.value, 0, "the value should have been clamped to the default minimum, zero");
+      checkValidity(input, false, apply, {low: -1, high: -1});
+
+      // Check that when the closest of the two steps that the value is between
+      // is greater than the maximum we sanitize to the lower step.
+      input = getFreshElement(test.type);
+      input.step = '2';
+      input.min = '1';
+      input.max = '10.9';
+      input.value = '10.8'; // closest step in 11, but 11 > maximum
+      is(input.value, 9, "check that the value coincides with a step");
+
+      // The way that step base is defined, the converse (the value not being
+      // on a step, and the nearest step being a value that would be underflow)
+      // is not possible, so nothing to test there.
+
+      is(input.validationMessage, "",
+         "The validation message should be empty.");
+      break;
     case 'time':
       // Tests invalid step values. That defaults to step = 1 minute (60).
       var values = [ '0', '-1', 'foo', 'any', 'ANY', 'aNy' ];
       for (var value of values) {
         input.step = value;
         input.value = '19:06:00';
         checkValidity(input, true, apply);
         input.value = '19:06:51';
--- a/content/html/content/test/forms/test_stepup_stepdown.html
+++ b/content/html/content/test/forms/test_stepup_stepdown.html
@@ -42,30 +42,30 @@ function checkAvailability()
     ["checkbox", false],
     ["radio", false],
     ["file", false],
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
+    ["range", true],
     ["date", true],
     ["time", true],
     // The next types have not been implemented but will fallback to "text"
     // which has the same value.
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["month", true],
     ["week", true],
     ["datetime-local", true],
-    ["range", true],
   ];
 
   var element = document.createElement("input");
   element.setAttribute('value', '0');
 
   for (data of testData) {
     var exceptionCaught = false;
     element.type = data[0];
@@ -175,16 +175,83 @@ function checkStepDown()
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,   true ],
     [ '0',  'ANY',  null,  null,  1,    null,   true ],
     [ '0',  'AnY',  null,  null,  1,    null,   true ],
     [ '0',  'aNy',  null,  null,  1,    null,   true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '-1',   false ],
   ]},
+  { type: 'range', data: [
+    // Regular case.
+    [ '1',   null,  null,  null,  null, '0',    false ],
+    // Argument testing.
+    [ '1',   null,  null,  null,  1,    '0',    false ],
+    [ '9',   null,  null,  null,  9,    '0',    false ],
+    [ '1',   null,  null,  null,  -1,   '2',    false ],
+    [ '1',   null,  null,  null,  0,    '1',    false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '1',   null,  null,  null,  1.1,  '0',    false ],
+    // With step values.
+    [ '1',  '0.5',  null,  null,  null, '0.5',  false ],
+    [ '1',  '0.25', null,  null,  4,    '0',    false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '1',  '0',    null,  null,  null, '0',    false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '1',  '-1',   null,  null,  null, '0',    false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '1',  'foo',  null,  null,  null, '0',    false ],
+    // Min values testing.
+    [ '1',  '1',    'foo', null,  null, '0',    false ],
+    [ '1',  null,   '-10', null,  null, '0',    false ],
+    [ '1',  null,   '0',   null,  null, '0',    false ],
+    [ '1',  null,   '10',  null,  null, '10',   false ],
+    [ '1',  null,   '2',   null,  null, '2',    false ],
+    [ '1',  null,   '1',   null,  null, '1',    false ],
+    // Max values testing.
+    [ '1',  '1',    null,  'foo', null, '0',    false ],
+    [ '1',  null,   null,  '10',  null, '0',    false ],
+    [ '1',  null,   null,  '0',   null, '0',    false ],
+    [ '1',  null,   null,  '-10', null, '0',    false ],
+    [ '1',  null,   null,  '1',   null, '0',    false ],
+    [ '5',  null,   null,  '3',   '3',  '0',    false ],
+    [ '5',  '2',    '-6',  '3',   '2',  '-2',   false ],
+    [ '-3', '5',    '-10', '-3',  null, '-10',  false ],
+    // Step mismatch.
+    [ '1',  '2',    '-2',  null,  null, '0',    false ],
+    [ '3',  '2',    '-2',  null,  null, '2',    false ],
+    [ '3',  '2',    '-2',  null,  '2',  '0',    false ],
+    [ '3',  '2',    '-2',  null,  '-2', '8',    false ],
+    [ '1',  '2',    '-6',  null,  null, '0',    false ],
+    [ '1',  '2',    '-2',  null,  null, '0',    false ],
+    [ '1',  '3',    '-6',  null,  null, '-3',   false ],
+    [ '2',  '3',    '-6',  null,  null, '0',    false ],
+    [ '2',  '3',    '1',   null,  null, '1',    false ],
+    [ '5',  '3',    '1',   null,  null, '1',    false ],
+    [ '3',  '2',    '-6',  null,  null, '2',    false ],
+    [ '5',  '2',    '-6',  null,  null, '4',    false ],
+    [ '6',  '2',   '1',    null,  null, '5',    false ],
+    [ '8',  '3',   '1',    null,  null, '4',    false ],
+    [ '9',  '2',   '-10',  null,  null, '8',    false ],
+    [ '7',  '3',   '-10',  null,  null, '5',    false ],
+    [ '-2', '3',   '-10',  null,  null, '-4',   false ],
+    // Clamping.
+    [ '0',  '2',    '-1',  null,  null, '-1',   false ],
+    [ '10', '2',    '0',   '4',   '10', '0',    false ],
+    [ '10', '2',    '0',   '4',   '5',  '0',    false ],
+    // value = "" (default will be 50).
+    [ '',   null,   null,  null,  null, '49',   false ],
+    // With step = 'any'.
+    [ '0',  'any',  null,  null,  1,    null,   true ],
+    [ '0',  'ANY',  null,  null,  1,    null,   true ],
+    [ '0',  'AnY',  null,  null,  1,    null,   true ],
+    [ '0',  'aNy',  null,  null,  1,    null,   true ],
+    // With @value = step base.
+    [ '1',  '2',    null,  null,  null, '1',    false ],
+  ]},
   { type: 'date', data: [
     // Regular case.
     [ '2012-07-09',  null,  null,  null,  null, '2012-07-08',   false ],
     // Argument testing.
     [ '2012-07-09',  null,  null,  null,  1,    '2012-07-08',   false ],
     [ '2012-07-09',  null,  null,  null,  5,    '2012-07-04',   false ],
     [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-10',   false ],
     [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
@@ -312,41 +379,45 @@ function checkStepDown()
   ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
-      if (data[0] != null) {
-        element.setAttribute('value', data[0]);
-      }
-
       if (data[1] != null) {
         element.step = data[1];
       }
 
       if (data[2] != null) {
         element.min = data[2];
       }
 
       if (data[3] != null) {
         element.max = data[3];
       }
 
+      // Set 'value' last for type=range, because the final sanitized value
+      // after setting 'step', 'min' and 'max' can be affected by the order in
+      // which those attributes are set. Setting 'value' last makes it simpler
+      // to reason about what the final value should be.
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
+
       var exceptionCaught = false;
       try {
         if (data[4] != null) {
           element.stepDown(data[4]);
         } else {
           element.stepDown();
         }
 
-        is(element.value, data[5], "The value should be " + data[5]);
+        is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
       } catch (e) {
         exceptionCaught = true;
         is(element.value, data[0], e.name + "The value should not have changed");
         is(e.name, 'InvalidStateError',
            "It should be a InvalidStateError exception.");
       } finally {
         is(exceptionCaught, data[6], "exception status should be " + data[6]);
       }
@@ -419,16 +490,80 @@ function checkStepUp()
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,  true ],
     [ '0',  'ANY',  null,  null,  1,    null,  true ],
     [ '0',  'AnY',  null,  null,  1,    null,  true ],
     [ '0',  'aNy',  null,  null,  1,    null,  true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '3',   false ],
   ]},
+  { type: 'range', data: [
+    // Regular case.
+    [ '1',   null,  null,  null,  null, '2',   false ],
+    // Argument testing.
+    [ '1',   null,  null,  null,  1,    '2',   false ],
+    [ '9',   null,  null,  null,  9,    '18',  false ],
+    [ '1',   null,  null,  null,  -1,   '0',   false ],
+    [ '1',   null,  null,  null,  0,    '1',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '1',   null,  null,  null,  1.1,  '2',   false ],
+    // With step values.
+    [ '1',  '0.5',  null,  null,  null, '1.5', false ],
+    [ '1',  '0.25', null,  null,  4,    '2',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '1',  '0',    null,  null,  null, '2',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '1',  '-1',   null,  null,  null, '2',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '1',  'foo',  null,  null,  null, '2',   false ],
+    // Min values testing.
+    [ '1',  '1',    'foo', null,  null, '2',   false ],
+    [ '1',  null,   '-10', null,  null, '2',   false ],
+    [ '1',  null,   '0',   null,  null, '2',   false ],
+    [ '1',  null,   '10',  null,  null, '11',  false ],
+    [ '1',  null,   '2',   null,  null, '3',   false ],
+    [ '1',  null,   '1',   null,  null, '2',   false ],
+    [ '0',  null,   '4',   null,  '5',  '9',   false ],
+    [ '0',  '2',    '5',   null,  '3',  '11',  false ],
+    // Max values testing.
+    [ '1',  '1',    null,  'foo', null, '2',   false ],
+    [ '1',  null,   null,  '10',  null, '2',   false ],
+    [ '1',  null,   null,  '0',   null, '0',   false ],
+    [ '1',  null,   null,  '-10', null, '0',   false ],
+    [ '1',  null,   null,  '1',   null, '1',   false ],
+    [ '-3', '5',    '-10', '-3',  null, '-5',  false ],
+    // Step mismatch.
+    [ '1',  '2',    '0',   null,  null, '4',   false ],
+    [ '1',  '2',    '0',   null,  '2',  '6',   false ],
+    [ '8',  '2',    null,  '9',   null, '8',   false ],
+    [ '-3', '2',    '-6',  null,  null, '0',   false ],
+    [ '9',  '3',    '-10', null,  null, '11',  false ],
+    [ '7',  '3',    '-10', null,  null, '11',  false ],
+    [ '7',  '3',    '5',   null,  null, '11',  false ],
+    [ '9',  '4',    '3',   null,  null, '15',  false ],
+    [ '-2', '3',    '-6',  null,  null, '0',   false ],
+    [ '7',  '3',    '6',   null,  null, '9',   false ],
+    // Clamping.
+    [ '1',  '2',    '0',  '3',   null,  '2',   false ],
+    [ '0',  '5',    '1',  '8',   '10',  '6',   false ],
+    [ '-9', '3',    '-8', '-1',  '5',   '-2',  false ],
+    [ '-9', '3',    '8',  '15',  '15',  '14',  false ],
+    [ '-1', '3',    '-1', '4',   '3',   '2',   false ],
+    [ '-3', '2',    '-6',  '-2',  null, '-2',  false ],
+    [ '-3', '2',    '-6',  '-1',  null, '-2',  false ],
+    // value = "" (default will be 50).
+    [ '',   null,   null,  null,  null, '51',  false ],
+    // With step = 'any'.
+    [ '0',  'any',  null,  null,  1,    null,  true ],
+    [ '0',  'ANY',  null,  null,  1,    null,  true ],
+    [ '0',  'AnY',  null,  null,  1,    null,  true ],
+    [ '0',  'aNy',  null,  null,  1,    null,  true ],
+    // With @value = step base.
+    [ '1',  '2',    null,  null,  null, '3',   false ],
+  ]},
   { type: 'date', data: [
     // Regular case.
     [ '2012-07-09',  null,  null,  null,  null, '2012-07-10',   false ],
     // Argument testing.
     [ '2012-07-09',  null,  null,  null,  1,    '2012-07-10',   false ],
     [ '2012-07-09',  null,  null,  null,  9,    '2012-07-18',   false ],
     [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-08',   false ],
     [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
@@ -559,41 +694,45 @@ function checkStepUp()
   ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
-      if (data[0] != null) {
-        element.setAttribute('value', data[0]);
-      }
-
       if (data[1] != null) {
         element.step = data[1];
       }
 
       if (data[2] != null) {
         element.min = data[2];
       }
 
       if (data[3] != null) {
         element.max = data[3];
       }
 
+      // Set 'value' last for type=range, because the final sanitized value
+      // after setting 'step', 'min' and 'max' can be affected by the order in
+      // which those attributes are set. Setting 'value' last makes it simpler
+      // to reason about what the final value should be.
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
+
       var exceptionCaught = false;
       try {
         if (data[4] != null) {
           element.stepUp(data[4]);
         } else {
           element.stepUp();
         }
 
-        is(element.value, data[5], "The value should be " + data[5]);
+        is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
       } catch (e) {
         exceptionCaught = true;
         is(element.value, data[0], e.name + "The value should not have changed");
         is(e.name, 'InvalidStateError',
            "It should be a InvalidStateError exception.");
       } finally {
         is(exceptionCaught, data[6], "exception status should be " + data[6]);
       }
--- a/content/html/content/test/forms/test_valueasdate_attribute.html
+++ b/content/html/content/test/forms/test_valueasdate_attribute.html
@@ -35,21 +35,21 @@ var validTypes =
   ["checkbox", false],
   ["radio", false],
   ["file", false],
   ["submit", false],
   ["image", false],
   ["reset", false],
   ["button", false],
   ["number", false],
+  ["range", false],
   ["date", true],
   ["time", true],
   // The next types have not been implemented but will fallback to "text"
   // which has the same value.
-  ["range", false],
   ["color", false],
 ];
 
 var todoTypes =
 [
   ["datetime", true],
   ["month", true],
   ["week", true],
--- a/content/html/content/test/forms/test_valueasnumber_attribute.html
+++ b/content/html/content/test/forms/test_valueasnumber_attribute.html
@@ -35,30 +35,30 @@ function checkAvailability()
     ["checkbox", false],
     ["radio", false],
     ["file", false],
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
+    ["range", true],
     ["date", true],
     ["time", true],
     // The next types have not been implemented but will fallback to "text"
     // which has the same value.
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["month", true],
     ["week", true],
     ["datetime-local", true],
-    ["range", true],
   ];
 
   var element = document.createElement('input');
 
   for (data of testData) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
@@ -177,16 +177,107 @@ function checkNumberSet()
       ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 }
 
+function checkRangeGet()
+{
+  // For type=range we should never get NaN since the user agent is required
+  // to fix up the input's value to be something sensible.
+
+  var min = -200;
+  var max = 200;
+  var defaultValue = min + (max - min)/2;
+
+  var testData =
+  [
+    ["42", 42],
+    ["-42", -42], // should work for negative values
+    ["42.1234", 42.1234],
+    ["123.12345678912345", 123.12345678912345], // double precision
+    ["1e2", 100], // e should be usable
+    ["2e1", 20],
+    ["1e-1", 0.1], // value after e can be negative
+    ["1E2", 100], // E can be used instead of e
+    ["e", defaultValue],
+    ["e2", defaultValue],
+    ["1e0.1", defaultValue],
+    ["", defaultValue],
+    ["foo", defaultValue],
+    ["42,13", defaultValue],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "range";
+  element.setAttribute("min", min); // avoids out of range sanitization
+  element.setAttribute("max", max);
+  element.setAttribute("step", "any"); // avoids step mismatch sanitization
+  for (data of testData) {
+    element.value = data[0];
+
+    // Given that NaN != NaN, we have to use null when the expected value is NaN.
+    is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+       "floating point representation of the value");
+  }
+}
+
+function checkRangeSet()
+{
+  var min = -200;
+  var max = 200;
+  var defaultValue = min + (max - min)/2;
+
+  var testData =
+  [
+    [42, "42"],
+    [-42, "-42"], // should work for negative values
+    [42.1234, "42.1234"],
+    [123.123456789123, "123.123456789123"], // double precision
+    [1e2, "100"], // e should be usable
+    [2e1, "20"],
+    [1e-1, "0.1"], // value after e can be negative
+    [1E2, "100"], // E can be used instead of e
+    ["foo", defaultValue],
+    ["", defaultValue],
+    [42, "42"], // Keep this here, it is used by the next test.
+    // Setting Infinity should throw and not change the current value.
+    [Infinity, "42", true],
+    [-Infinity, "42", true],
+    // Setting NaN should change the value to the empty string.
+    [NaN, defaultValue],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "range";
+  element.setAttribute("min", min); // avoids out of range sanitization
+  element.setAttribute("max", max);
+  element.setAttribute("step", "any"); // avoids step mismatch sanitization
+  for (data of testData) {
+    var caught = false;
+    try {
+      element.valueAsNumber = data[0];
+      is(element.value, data[1],
+         "valueAsNumber should be able to set the value");
+    } catch (e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have thrown");
+      is(element.value, data[1], "value should not have changed");
+    } else {
+      ok(!caught, "valueAsNumber should not have thrown");
+    }
+  }
+}
+
 function checkDateGet()
 {
   var validData =
   [
     [ "2012-07-12", 1342051200000 ],
     [ "1970-01-01", 0 ],
     // We are supposed to support at least until this date.
     // (corresponding to the date object maximal value)
@@ -397,16 +488,20 @@ function checkTimeSet()
 }
 
 checkAvailability();
 
 // <input type='number'> test
 checkNumberGet();
 checkNumberSet();
 
+// <input type='range'> test
+checkRangeGet();
+checkRangeSet();
+
 // <input type='date'> test
 checkDateGet();
 checkDateSet();
 
 // <input type='time'> test
 checkTimeGet();
 checkTimeSet();
 
--- a/content/html/content/test/test_bug590363.html
+++ b/content/html/content/test/test_bug590363.html
@@ -32,34 +32,44 @@ var testData = [
   [ "text",     true ],
   [ "url",      true ],
   [ "email",    true ],
   [ "search",   true ],
   [ "password", true ],
   [ "number",   true ],
   [ "date",     true ],
   [ "time",     true ],
+  [ "range",    true ],
   // 'file' is treated separatly.
 ];
 
 var todoTypes = [
-  "datetime", "month", "week", "datetime-local", "range", "color"
+  "datetime", "month", "week", "datetime-local", "color"
 ];
 
 var nonTrivialSanitizing = [ 'number', 'date', 'time' ];
 
 var length = testData.length;
 for (var i=0; i<length; ++i) {
   for (var j=0; j<length; ++j) {
     var e = document.createElement('input');
     e.type = testData[i][0];
 
     var expectedValue;
-    if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
-        nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
+
+    // range will sanitize its value to 50 (the default) if it isn't a valid
+    // number. We need to handle that specially.
+    if (testData[j][0] == 'range' || testData[i][0] == 'range') {
+      if (testData[j][0] == 'date' || testData[j][0] == 'time') {
+        expectedValue = '';
+      } else {
+        expectedValue = '50';
+      }
+    } else if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
+               nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
       expectedValue = '';
     } else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
       expectedValue = '42';
     } else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
       expectedValue = '2012-12-21';
     } else if (testData[i][0] == 'time' || testData[j][0] == 'time') {
       expectedValue = '21:21';
     } else {
@@ -77,17 +87,20 @@ for (var i=0; i<length; ++i) {
 // We are just going to check that we do not loose the value.
 for (var data of testData) {
   var e = document.createElement('input');
   e.type = data[0];
   e.value = 'foo';
   e.type = 'file';
   e.type = data[0];
 
-  if (data[1]) {
+  if (data[0] == 'range') {
+    is(e.value, '50', ".value should still return the same value after " +
+       "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+  } else if (data[1]) {
     is(e.value, '', ".value should have been reset to the empty string after " +
        "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
   } else {
     is(e.value, 'foo', ".value should still return the same value after " +
        "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
   }
 }
 
--- a/content/html/content/test/test_bug598643.html
+++ b/content/html/content/test/test_bug598643.html
@@ -45,19 +45,19 @@ function testFileControl(aElement)
      "the file control shouldn't suffer from being too long");
 }
 
 var types = [
   // These types can be too long.
   [ "text", "email", "password", "url", "search", "tel" ],
   // These types can't be too long.
   [ "radio", "checkbox", "submit", "button", "reset", "image", "hidden",
-    'number', 'date', 'time' ],
+    'number', 'range', 'date', 'time' ],
   // These types can't be too long but are not implemented yet.
-  [ "range", "color", "datetime", "month", "week", 'datetime-local' ]
+  [ "color", "datetime", "month", "week", 'datetime-local' ]
 ];
 
 var input = document.createElement("input");
 input.maxLength = 1;
 input.value = "foo";
 
 // Too long types.
 for (type of types[0]) {
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -17,23 +17,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMMediaStream)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMMediaStream)
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(DOMMediaStream, mWindow)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMLocalMediaStream)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMLocalMediaStream)
-NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
-
-NS_IMPL_ADDREF_INHERITED(DOMLocalMediaStream, DOMMediaStream)
-NS_IMPL_RELEASE_INHERITED(DOMLocalMediaStream, DOMMediaStream)
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(DOMLocalMediaStream, DOMMediaStream)
+NS_IMPL_ISUPPORTS_INHERITED1(DOMLocalMediaStream, DOMMediaStream,
+                             nsIDOMLocalMediaStream)
 
 DOMMediaStream::~DOMMediaStream()
 {
   if (mStream) {
     mStream->Destroy();
   }
 }
 
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -113,17 +113,16 @@ protected:
 class DOMLocalMediaStream : public DOMMediaStream,
                             public nsIDOMLocalMediaStream
 {
 public:
   DOMLocalMediaStream() {}
   virtual ~DOMLocalMediaStream();
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMLocalMediaStream, DOMMediaStream)
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap);
 
   virtual void Stop();
 
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
    */
--- a/dom/base/nsScreen.cpp
+++ b/dom/base/nsScreen.cpp
@@ -85,20 +85,18 @@ nsScreen::~nsScreen()
 {
   Reset();
   hal::UnregisterScreenConfigurationObserver(this);
 }
 
 
 DOMCI_DATA(Screen, nsScreen)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(nsScreen, nsDOMEventTargetHelper)
-
 // QueryInterface implementation for nsScreen
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsScreen)
+NS_INTERFACE_MAP_BEGIN(nsScreen)
   NS_INTERFACE_MAP_ENTRY(nsIDOMScreen)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Screen)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsScreen, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsScreen, nsDOMEventTargetHelper)
 
 NS_IMPL_EVENT_HANDLER(nsScreen, mozorientationchange)
--- a/dom/bluetooth/BluetoothManager.cpp
+++ b/dom/bluetooth/BluetoothManager.cpp
@@ -20,19 +20,17 @@
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 
 using namespace mozilla;
 
 USING_BLUETOOTH_NAMESPACE
 
 DOMCI_DATA(BluetoothManager, BluetoothManager)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(BluetoothManager, nsDOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothManager)
+NS_INTERFACE_MAP_BEGIN(BluetoothManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMBluetoothManager)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(BluetoothManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothManager, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothManager, nsDOMEventTargetHelper)
 
 class GetAdapterTask : public BluetoothReplyRunnable
--- a/dom/bluetooth/BluetoothManager.h
+++ b/dom/bluetooth/BluetoothManager.h
@@ -23,19 +23,16 @@ class BluetoothManager : public nsDOMEve
                        , public BluetoothPropertyContainer
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMBLUETOOTHMANAGER
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothManager,
-                                           nsDOMEventTargetHelper)
-
   static already_AddRefed<BluetoothManager>
   Create(nsPIDOMWindow* aWindow);
   void Notify(const BluetoothSignal& aData);
   virtual void SetPropertyByValue(const BluetoothNamedValue& aValue);
 private:
   BluetoothManager(nsPIDOMWindow* aWindow);
   ~BluetoothManager();
 };
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -12,26 +12,24 @@
 #include "DOMCameraCapabilities.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
 
-NS_IMPL_CYCLE_COLLECTION_0(DOMCameraCapabilities)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMCameraCapabilities)
+NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMCameraCapabilities)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMCameraCapabilities)
+NS_IMPL_ADDREF(DOMCameraCapabilities)
+NS_IMPL_RELEASE(DOMCameraCapabilities)
 
 static nsresult
 ParseZoomRatioItemAndAdd(JSContext* aCx, JSObject* aArray, uint32_t aIndex, const char* aStart, char** aEnd)
 {
   if (!*aEnd) {
     // make 'aEnd' follow the same semantics as strchr().
     aEnd = nullptr;
   }
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -1,29 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERACAPABILITIES_H
 #define DOM_CAMERA_DOMCAMERACAPABILITIES_H
 
-#include "nsCycleCollectionParticipant.h"
 #include "ICameraControl.h"
 #include "nsAutoPtr.h"
 #include "CameraCommon.h"
 
 namespace mozilla {
 
 typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JSObject* aArray, uint32_t aIndex, const char* aStart, char** aEnd);
 
 class DOMCameraCapabilities MOZ_FINAL : public nsICameraCapabilities
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(DOMCameraCapabilities)
+  NS_DECL_ISUPPORTS
   NS_DECL_NSICAMERACAPABILITIES
 
   DOMCameraCapabilities(ICameraControl* aCamera)
     : mCamera(aCamera)
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
--- a/dom/camera/FallbackCameraCapabilities.cpp
+++ b/dom/camera/FallbackCameraCapabilities.cpp
@@ -6,26 +6,24 @@
 #include "DOMCameraControl.h"
 #include "DOMCameraCapabilities.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
 
-NS_IMPL_CYCLE_COLLECTION_0(DOMCameraCapabilities)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMCameraCapabilities)
+NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMCameraCapabilities)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMCameraCapabilities)
+NS_IMPL_ADDREF(DOMCameraCapabilities)
+NS_IMPL_RELEASE(DOMCameraCapabilities)
 
 /* [implicit_jscontext] readonly attribute jsval previewSizes; */
 NS_IMETHODIMP
 DOMCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -146,17 +146,19 @@ nsDOMCameraControl::nsDOMCameraControl(u
    *
    * Once it is initialized, the GetCameraResult main-thread runnable will
    * decrement it again to make sure it can be cleaned up.
    *
    * nsGonkCameraControl MUST NOT hold a strong reference to this
    * nsDOMCameraControl or memory will leak!
    */
   NS_ADDREF_THIS();
-  mCameraControl = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindowId);
+  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindowId);
+  control->DispatchInit(this, onSuccess, onError, aWindowId);
+  mCameraControl = control;
 }
 
 // Gonk-specific CameraControl implementation.
 
 // Initialize nsGonkCameraControl instance--runs on camera thread.
 class InitGonkCameraControl : public nsRunnable
 {
 public:
@@ -211,17 +213,20 @@ nsGonkCameraControl::nsGonkCameraControl
   , mRecorder(nullptr)
   , mProfileManager(nullptr)
   , mRecorderProfile(nullptr)
   , mVideoFile(nullptr)
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock");
+}
 
+void nsGonkCameraControl::DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
+{
   // ...but initialization is carried out on the camera thread.
   nsCOMPtr<nsIRunnable> init = new InitGonkCameraControl(this, aDOMCameraControl, onSuccess, onError, aWindowId);
   mCameraThread->Dispatch(init, NS_DISPATCH_NORMAL);
 }
 
 nsresult
 nsGonkCameraControl::Init()
 {
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -35,16 +35,17 @@ class GraphicBufferLocked;
 
 class GonkRecorderProfile;
 class GonkRecorderProfileManager;
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
+  void DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
   nsresult Init();
 
   const char* GetParameter(const char* aKey);
   const char* GetParameterConstChar(uint32_t aKey);
   double GetParameterDouble(uint32_t aKey);
   void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
   void GetParameter(uint32_t aKey, nsTArray<idl::CameraSize>& aSizes);
   void SetParameter(const char* aKey, const char* aValue);
--- a/dom/fm/FMRadio.cpp
+++ b/dom/fm/FMRadio.cpp
@@ -59,23 +59,17 @@ FMRadio::~FMRadio()
   UnregisterFMRadioObserver(this);
   if (!mHasInternalAntenna) {
     UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
   }
 }
 
 DOMCI_DATA(FMRadio, FMRadio)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(FMRadio, nsDOMEventTargetHelper)
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FMRadio,
-                                               nsDOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
+NS_INTERFACE_MAP_BEGIN(FMRadio)
   NS_INTERFACE_MAP_ENTRY(nsIFMRadio)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(FMRadio)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_EVENT_HANDLER(FMRadio, seekcomplete)
 NS_IMPL_EVENT_HANDLER(FMRadio, disabled)
 NS_IMPL_EVENT_HANDLER(FMRadio, enabled)
 NS_IMPL_EVENT_HANDLER(FMRadio, antennastatechange)
--- a/dom/fm/FMRadio.h
+++ b/dom/fm/FMRadio.h
@@ -25,24 +25,21 @@ namespace fm {
 /* Header file */
 class FMRadio : public nsDOMEventTargetHelper
               , public nsIFMRadio
               , public hal::FMRadioObserver
               , public hal::SwitchObserver
               , public nsIAudioChannelAgentCallback
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIFMRADIO
   NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
-                                                   FMRadio,
-                                                   nsDOMEventTargetHelper)
   FMRadio();
   virtual void Notify(const hal::FMRadioOperationInformation& info);
   virtual void Notify(const hal::SwitchEvent& aEvent);
 
 private:
   ~FMRadio();
 
   hal::SwitchState mHeadphoneState;
--- a/dom/network/src/Connection.cpp
+++ b/dom/network/src/Connection.cpp
@@ -21,19 +21,17 @@ DOMCI_DATA(MozConnection, mozilla::dom::
 
 namespace mozilla {
 namespace dom {
 namespace network {
 
 const char* Connection::sMeteredPrefName     = "dom.network.metered";
 const bool  Connection::sMeteredDefaultValue = false;
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(Connection, nsDOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Connection)
+NS_INTERFACE_MAP_BEGIN(Connection)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozConnection)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozConnection)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(Connection, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Connection, nsDOMEventTargetHelper)
 
 NS_IMPL_EVENT_HANDLER(Connection, change)
--- a/dom/network/src/Connection.h
+++ b/dom/network/src/Connection.h
@@ -21,32 +21,29 @@ class NetworkInformation;
 namespace dom {
 namespace network {
 
 class Connection : public nsDOMEventTargetHelper
                  , public nsIDOMMozConnection
                  , public NetworkObserver
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMMOZCONNECTION
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
   Connection();
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
   // For IObserver
   void Notify(const hal::NetworkInformation& aNetworkInfo);
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Connection,
-                                           nsDOMEventTargetHelper)
-
 private:
   /**
    * Update the connection information stored in the object using a
    * NetworkInformation object.
    */
   void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo);
 
   /**
--- a/dom/sms/src/SmsManager.cpp
+++ b/dom/sms/src/SmsManager.cpp
@@ -29,19 +29,17 @@
 #define DELIVERY_ERROR_EVENT_NAME   NS_LITERAL_STRING("deliveryerror")
 
 DOMCI_DATA(MozSmsManager, mozilla::dom::sms::SmsManager)
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(SmsManager, nsDOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SmsManager)
+NS_INTERFACE_MAP_BEGIN(SmsManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsManager)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(SmsManager, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SmsManager, nsDOMEventTargetHelper)
 
--- a/dom/sms/src/SmsManager.h
+++ b/dom/sms/src/SmsManager.h
@@ -16,25 +16,22 @@ namespace mozilla {
 namespace dom {
 namespace sms {
 
 class SmsManager : public nsDOMEventTargetHelper
                  , public nsIDOMMozSmsManager
                  , public nsIObserver
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMMOZSMSMANAGER
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SmsManager,
-                                           nsDOMEventTargetHelper)
-
   static already_AddRefed<SmsManager>
   CreateInstanceIfAllowed(nsPIDOMWindow *aWindow);
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
 private:
   /**
--- a/dom/voicemail/Voicemail.cpp
+++ b/dom/voicemail/Voicemail.cpp
@@ -15,19 +15,17 @@
 #include "nsServiceManagerUtils.h"
 #include "GeneratedEvents.h"
 
 DOMCI_DATA(MozVoicemail, mozilla::dom::Voicemail)
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_0(Voicemail, nsDOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Voicemail)
+NS_INTERFACE_MAP_BEGIN(Voicemail)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozVoicemail)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozVoicemail)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(Voicemail, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Voicemail, nsDOMEventTargetHelper)
 
 NS_IMPL_ISUPPORTS1(Voicemail::RILVoicemailCallback, nsIRILVoicemailCallback)
--- a/dom/voicemail/Voicemail.h
+++ b/dom/voicemail/Voicemail.h
@@ -24,18 +24,16 @@ class Voicemail : public nsDOMEventTarge
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMMOZVOICEMAIL
   NS_DECL_NSIRILVOICEMAILCALLBACK
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Voicemail, nsDOMEventTargetHelper)
-
   Voicemail(nsPIDOMWindow* aWindow, nsIRILContentHelper* aRIL);
   virtual ~Voicemail();
 
 private:
   nsCOMPtr<nsIRILContentHelper> mRIL;
   nsCOMPtr<nsIRILVoicemailCallback> mRILVoicemailCallback;
 
   class RILVoicemailCallback : public nsIRILVoicemailCallback
--- a/editor/libeditor/base/EditTxn.cpp
+++ b/editor/libeditor/base/EditTxn.cpp
@@ -2,17 +2,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "EditTxn.h"
 #include "nsError.h"
 #include "nsISupportsBase.h"
 
-NS_IMPL_CYCLE_COLLECTION_0(EditTxn)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(EditTxn)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditTxn)
+  // We don't have anything to traverse, but some of our subclasses do.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditTxn)
   NS_INTERFACE_MAP_ENTRY(nsITransaction)
   NS_INTERFACE_MAP_ENTRY(nsPIEditorTransaction)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransaction)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditTxn)
--- a/embedding/components/webbrowserpersist/src/nsWebBrowserPersist.cpp
+++ b/embedding/components/webbrowserpersist/src/nsWebBrowserPersist.cpp
@@ -3266,16 +3266,17 @@ nsWebBrowserPersist::CloneNodeWithFixedU
             nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
             switch (formControl->GetType()) {
                 case NS_FORM_INPUT_EMAIL:
                 case NS_FORM_INPUT_SEARCH:
                 case NS_FORM_INPUT_TEXT:
                 case NS_FORM_INPUT_TEL:
                 case NS_FORM_INPUT_URL:
                 case NS_FORM_INPUT_NUMBER:
+                case NS_FORM_INPUT_RANGE:
                 case NS_FORM_INPUT_DATE:
                 case NS_FORM_INPUT_TIME:
                     nodeAsInput->GetValue(valueStr);
                     // Avoid superfluous value="" serialization
                     if (valueStr.IsEmpty())
                       outElt->RemoveAttribute(valueAttr);
                     else
                       outElt->SetAttribute(valueAttr, valueStr);
--- a/extensions/spellcheck/src/mozGenericWordUtils.cpp
+++ b/extensions/spellcheck/src/mozGenericWordUtils.cpp
@@ -1,24 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "mozGenericWordUtils.h"
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(mozGenericWordUtils)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(mozGenericWordUtils)
-
-NS_INTERFACE_MAP_BEGIN(mozGenericWordUtils)
-  NS_INTERFACE_MAP_ENTRY(mozISpellI18NUtil)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozISpellI18NUtil)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozGenericWordUtils)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION_0(mozGenericWordUtils)
+NS_IMPL_ISUPPORTS1(mozGenericWordUtils, mozISpellI18NUtil)
 
   // do something sensible but generic ... eventually.  For now whine.
 
 mozGenericWordUtils::mozGenericWordUtils()
 {
   /* member initializers and constructor code */
 }
 
--- a/extensions/spellcheck/src/mozGenericWordUtils.h
+++ b/extensions/spellcheck/src/mozGenericWordUtils.h
@@ -8,17 +8,16 @@
 
 #include "nsCOMPtr.h"
 #include "mozISpellI18NUtil.h"
 #include "nsCycleCollectionParticipant.h"
 
 class mozGenericWordUtils : public mozISpellI18NUtil
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_ISUPPORTS
   NS_DECL_MOZISPELLI18NUTIL
-  NS_DECL_CYCLE_COLLECTION_CLASS(mozGenericWordUtils)
 
   mozGenericWordUtils();
   virtual ~mozGenericWordUtils();
 };
 
 #endif
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -473,21 +473,20 @@ NS_INTERFACE_MAP_BEGIN(mozInlineSpellChe
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozInlineSpellChecker)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozInlineSpellChecker)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozInlineSpellChecker)
 
-NS_IMPL_CYCLE_COLLECTION_5(mozInlineSpellChecker,
+NS_IMPL_CYCLE_COLLECTION_4(mozInlineSpellChecker,
                            mSpellCheck,
                            mTextServicesDocument,
                            mTreeWalker,
-                           mConverter,
                            mCurrentSelectionAnchorNode)
 
 mozInlineSpellChecker::SpellCheckingState
   mozInlineSpellChecker::gCanEnableSpellChecking =
   mozInlineSpellChecker::SpellCheck_Uninitialized;
 
 mozInlineSpellChecker::mozInlineSpellChecker() :
     mNumWordsInSpellSelection(0),
--- a/extensions/spellcheck/src/mozSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozSpellChecker.cpp
@@ -16,18 +16,17 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpell
 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
 
 NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_3(mozSpellChecker,
-                           mConverter,
+NS_IMPL_CYCLE_COLLECTION_2(mozSpellChecker,
                            mTsDoc,
                            mPersonalDictionary)
 
 mozSpellChecker::mozSpellChecker()
 {
 }
 
 mozSpellChecker::~mozSpellChecker()
--- a/extensions/spellcheck/src/mozSpellI18NManager.cpp
+++ b/extensions/spellcheck/src/mozSpellI18NManager.cpp
@@ -3,26 +3,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 "mozSpellI18NManager.h"
 #include "mozEnglishWordUtils.h"
 #include "mozGenericWordUtils.h"
 #include "nsString.h"
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellI18NManager)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellI18NManager)
-
-NS_INTERFACE_MAP_BEGIN(mozSpellI18NManager)
-  NS_INTERFACE_MAP_ENTRY(mozISpellI18NManager)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozISpellI18NManager)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellI18NManager)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION_0(mozSpellI18NManager)
+NS_IMPL_ISUPPORTS1(mozSpellI18NManager, mozISpellI18NManager)
 
 mozSpellI18NManager::mozSpellI18NManager()
 {
   /* member initializers and constructor code */
 }
 
 mozSpellI18NManager::~mozSpellI18NManager()
 {
--- a/extensions/spellcheck/src/mozSpellI18NManager.h
+++ b/extensions/spellcheck/src/mozSpellI18NManager.h
@@ -14,17 +14,16 @@
 #define MOZ_SPELLI18NMANAGER_CID         \
 { /* {AEB8936F-219C-4D3C-8385-D9382DAA551A} */  \
 0xaeb8936f, 0x219c, 0x4d3c, \
   { 0x83, 0x85, 0xd9, 0x38, 0x2d, 0xaa, 0x55, 0x1a } }
 
 class mozSpellI18NManager : public mozISpellI18NManager
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_ISUPPORTS
   NS_DECL_MOZISPELLI18NMANAGER
-  NS_DECL_CYCLE_COLLECTION_CLASS(mozSpellI18NManager)
 
   mozSpellI18NManager();
   virtual ~mozSpellI18NManager();
 };
 #endif
 
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -1,9 +1,9 @@
-asserts(0-1) load ownerdiscard.html # bug 804291
+asserts(0-2) load ownerdiscard.html # bug 804291, bug 807211
 
 load 83804-1.gif
 load 89341-1.gif
 
 # the following tests were inspired by bug 525326
 # they have image sizes of 65535x65535 which is larger than we allow
 load invalid-size.gif
 # this image has a valid size for the first frame, but the second frame is 65535x65535
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -271,16 +271,19 @@ NS_NewLeafBoxFrame (nsIPresShell* aPresS
 
 nsIFrame*
 NS_NewStackFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 nsIFrame*
 NS_NewProgressMeterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 nsIFrame*
+NS_NewRangeFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsIFrame*
 NS_NewImageBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 nsIFrame*
 NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 nsIFrame*
 NS_NewGroupBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
@@ -3470,16 +3473,17 @@ nsCSSFrameConstructor::FindInputData(Ele
     SIMPLE_INT_CREATE(NS_FORM_INPUT_FILE, NS_NewFileControlFrame),
     SIMPLE_INT_CHAIN(NS_FORM_INPUT_IMAGE,
                      nsCSSFrameConstructor::FindImgControlData),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_EMAIL, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_SEARCH, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TEXT, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TEL, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_URL, NS_NewTextControlFrame),
+    SIMPLE_INT_CREATE(NS_FORM_INPUT_RANGE, NS_NewRangeFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_PASSWORD, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 635240.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 773205.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 773205
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
     { NS_FORM_INPUT_SUBMIT,
--- a/layout/forms/Makefile.in
+++ b/layout/forms/Makefile.in
@@ -37,16 +37,17 @@ CPPSRCS		= \
 		nsComboboxControlFrame.cpp \
 		nsFieldSetFrame.cpp \
 		nsFileControlFrame.cpp \
 		nsFormControlFrame.cpp \
 		nsGfxButtonControlFrame.cpp \
 		nsGfxCheckboxControlFrame.cpp \
 		nsGfxRadioControlFrame.cpp \
 		nsProgressFrame.cpp \
+		nsRangeFrame.cpp \
 		nsMeterFrame.cpp \
 		nsTextControlFrame.cpp \
 		nsHTMLButtonControlFrame.cpp \
 		nsImageControlFrame.cpp \
 		nsLegendFrame.cpp \
 		nsListControlFrame.cpp \
 		nsSelectsAreaFrame.cpp \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsRangeFrame.cpp
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRangeFrame.h"
+
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsFontMetrics.h"
+#include "nsFormControlFrame.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsINameSpaceManager.h"
+#include "nsINodeInfo.h"
+#include "nsIPresShell.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLInputElement.h"
+#include "nsPresContext.h"
+#include "nsNodeInfoManager.h"
+#include "mozilla/dom/Element.h"
+#include "prtypes.h"
+
+#include <algorithm>
+
+#define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
+
+nsIFrame*
+NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+  return new (aPresShell) nsRangeFrame(aContext);
+}
+
+nsRangeFrame::nsRangeFrame(nsStyleContext* aContext)
+  : nsContainerFrame(aContext)
+{
+}
+
+nsRangeFrame::~nsRangeFrame()
+{
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
+
+NS_QUERYFRAME_HEAD(nsRangeFrame)
+  NS_QUERYFRAME_ENTRY(nsRangeFrame)
+  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void
+nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+               "nsRangeFrame should not have continuations; if it does we "
+               "need to call RegUnregAccessKey only for the first.");
+  nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+  nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
+  nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
+  nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+  // Get the NodeInfoManager and tag necessary to create the anonymous divs.
+  nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
+
+  // Create the track div:
+  nsCOMPtr<nsINodeInfo> nodeInfo;
+  nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
+                                                 kNameSpaceID_XHTML,
+                                                 nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+  nsresult rv = NS_NewHTMLElement(getter_AddRefs(mTrackDiv), nodeInfo.forget(),
+                                  mozilla::dom::NOT_FROM_PARSER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Associate ::-moz-range-track pseudo-element to the anonymous child.
+  nsCSSPseudoElements::Type pseudoType =
+    nsCSSPseudoElements::ePseudo_mozRangeTrack;
+  nsRefPtr<nsStyleContext> newStyleContext =
+    PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
+                                                         pseudoType,
+                                                         StyleContext());
+
+  if (!aElements.AppendElement(ContentInfo(mTrackDiv, newStyleContext))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // Create the thumb div:
+  nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
+                                                 kNameSpaceID_XHTML,
+                                                 nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+  rv = NS_NewHTMLElement(getter_AddRefs(mThumbDiv), nodeInfo.forget(),
+                         mozilla::dom::NOT_FROM_PARSER);
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Associate ::-moz-range-thumb pseudo-element to the anonymous child.
+  pseudoType = nsCSSPseudoElements::ePseudo_mozRangeThumb;
+  newStyleContext =
+    PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
+                                                         pseudoType,
+                                                         StyleContext());
+
+  if (!aElements.AppendElement(ContentInfo(mThumbDiv, newStyleContext))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+void
+nsRangeFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
+                                       uint32_t aFilter)
+{
+  aElements.MaybeAppendElement(mTrackDiv);
+  aElements.MaybeAppendElement(mThumbDiv);
+}
+
+void
+nsRangeFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                               const nsRect&           aDirtyRect,
+                               const nsDisplayListSet& aLists)
+{
+  BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
+}
+
+NS_IMETHODIMP
+nsRangeFrame::Reflow(nsPresContext*           aPresContext,
+                     nsHTMLReflowMetrics&     aDesiredSize,
+                     const nsHTMLReflowState& aReflowState,
+                     nsReflowStatus&          aStatus)
+{
+  DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
+  DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
+
+  NS_ASSERTION(mTrackDiv, "Track div must exist!");
+  NS_ASSERTION(mThumbDiv, "Thumb div must exist!");
+  NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+               "nsRangeFrame should not have continuations; if it does we "
+               "need to call RegUnregAccessKey only for the first.");
+
+  if (mState & NS_FRAME_FIRST_REFLOW) {
+    nsFormControlFrame::RegUnRegAccessKey(this, true);
+  }
+
+  nscoord computedHeight = aReflowState.ComputedHeight();
+  if (computedHeight == NS_AUTOHEIGHT) {
+    computedHeight = 0;
+  }
+  aDesiredSize.width = aReflowState.ComputedWidth() +
+                       aReflowState.mComputedBorderPadding.LeftRight();
+  aDesiredSize.height = computedHeight +
+                        aReflowState.mComputedBorderPadding.TopBottom();
+
+  nsresult rv =
+    ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowState);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+  nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+  if (trackFrame) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
+  }
+
+  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+  if (thumbFrame) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
+  }
+
+  FinishAndStoreOverflow(&aDesiredSize);
+
+  aStatus = NS_FRAME_COMPLETE;
+
+  NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
+
+  return NS_OK;
+}
+
+nsresult
+nsRangeFrame::ReflowAnonymousContent(nsPresContext*           aPresContext,
+                                     nsHTMLReflowMetrics&     aDesiredSize,
+                                     const nsHTMLReflowState& aReflowState)
+{
+  if (ShouldUseNativeStyle()) {
+    return NS_OK; // No need to reflow since we're not using these frames
+  }
+
+  // The width/height of our content box, which is the available width/height
+  // for our anonymous content:
+  nscoord rangeFrameContentBoxWidth = aReflowState.ComputedWidth();
+  nscoord rangeFrameContentBoxHeight = aReflowState.ComputedHeight();
+  if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) {
+    rangeFrameContentBoxHeight = 0;
+  }
+
+  nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+
+  if (trackFrame) { // display:none?
+
+    // Position the track:
+    // The idea here is that we allow content authors to style the width,
+    // height, border and padding of the track, but we ignore margin and
+    // positioning properties and do the positioning ourself to keep the center
+    // of the track's border box on the center of the nsRangeFrame's content
+    // box.
+
+    nsHTMLReflowState trackReflowState(aPresContext, aReflowState, trackFrame,
+                                       nsSize(aReflowState.ComputedWidth(),
+                                              NS_UNCONSTRAINEDSIZE));
+
+    // Find the x/y position of the track frame such that it will be positioned
+    // as described above. These coordinates are with respect to the
+    // nsRangeFrame's border-box.
+    nscoord trackX = rangeFrameContentBoxWidth / 2;
+    nscoord trackY = rangeFrameContentBoxHeight / 2;
+
+    // Account for the track's border and padding (we ignore its margin):
+    trackX -= trackReflowState.mComputedBorderPadding.left +
+                trackReflowState.ComputedWidth() / 2;
+    trackY -= trackReflowState.mComputedBorderPadding.top +
+                trackReflowState.ComputedHeight() / 2;
+
+    // Make relative to our border box instead of our content box:
+    trackX += aReflowState.mComputedBorderPadding.left;
+    trackY += aReflowState.mComputedBorderPadding.top;
+
+    nsReflowStatus frameStatus = NS_FRAME_COMPLETE;
+    nsHTMLReflowMetrics trackDesiredSize;
+    nsresult rv = ReflowChild(trackFrame, aPresContext, trackDesiredSize,
+                              trackReflowState, trackX, trackY, 0, frameStatus);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+               "We gave our child unconstrained height, so it should be complete");
+    rv = FinishReflowChild(trackFrame, aPresContext, &trackReflowState,
+                           trackDesiredSize, trackX, trackY, 0);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+
+  if (thumbFrame) { // display:none?
+
+    // Position the thumb:
+    // The idea here is that we want to position the thumb so that the center
+    // of the thumb is on an imaginary line drawn from the middle of one edge
+    // of the range frame's content box to the middle of the opposite edge of
+    // its content box (the opposite edges being the left/right edge if the
+    // range is horizontal, or else the top/bottom edges if the range is
+    // vertical). How far along this line the center of the thumb is placed
+    // depends on the value of the range.
+
+    nsSize frameSizeOverride(aDesiredSize.width, aDesiredSize.height);
+    bool isHorizontal = IsHorizontal(&frameSizeOverride);
+
+    double valueAsFraction = GetValueAsFractionOfRange();
+    MOZ_ASSERT(valueAsFraction >= 0.0 && valueAsFraction <= 1.0);
+
+    nsHTMLReflowState thumbReflowState(aPresContext, aReflowState, thumbFrame,
+                                       nsSize(aReflowState.ComputedWidth(),
+                                              NS_UNCONSTRAINEDSIZE));
+
+    // Find the x/y position of the thumb frame such that it will be positioned
+    // as described above. These coordinates are with respect to the
+    // nsRangeFrame's border-box.
+    nscoord thumbX, thumbY;
+
+    if (isHorizontal) {
+      thumbX = NSToCoordRound(rangeFrameContentBoxWidth * valueAsFraction);
+      thumbY = rangeFrameContentBoxHeight / 2;
+    } else {
+      thumbX = rangeFrameContentBoxWidth / 2;
+      // For vertical range zero is at the bottom, so subtract from height:
+      thumbY = rangeFrameContentBoxHeight -
+                 NSToCoordRound(rangeFrameContentBoxHeight * valueAsFraction);
+    }
+
+    thumbX -= thumbReflowState.mComputedBorderPadding.left +
+                thumbReflowState.ComputedWidth() / 2;
+    thumbY -= thumbReflowState.mComputedBorderPadding.top +
+                thumbReflowState.ComputedHeight() / 2;
+
+    // Make relative to our border box instead of our content box:
+    thumbX += aReflowState.mComputedBorderPadding.left;
+    thumbY += aReflowState.mComputedBorderPadding.top;
+
+    nsReflowStatus frameStatus = NS_FRAME_COMPLETE;
+    nsHTMLReflowMetrics thumbDesiredSize;
+    nsresult rv = ReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
+                              thumbReflowState, thumbX, thumbY, 0, frameStatus);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+               "We gave our child unconstrained height, so it should be complete");
+    rv = FinishReflowChild(thumbFrame, aPresContext, &thumbReflowState,
+                           thumbDesiredSize, thumbX, thumbY, 0);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+double
+nsRangeFrame::GetValueAsFractionOfRange()
+{
+  MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
+  nsHTMLInputElement* input = static_cast<nsHTMLInputElement*>(mContent);
+
+  MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
+
+  double value = input->GetValueAsDouble();
+  double minimum = input->GetMinimum();
+  double maximum = input->GetMaximum();
+
+  MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(value) &&
+             MOZ_DOUBLE_IS_FINITE(minimum) &&
+             MOZ_DOUBLE_IS_FINITE(maximum),
+             "type=range should have a default maximum/minimum");
+  
+  if (maximum <= minimum) {
+    MOZ_ASSERT(value == minimum, "Unsanitized value");
+    return 0.0;
+  }
+  
+  MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
+  
+  return (value - minimum) / (maximum - minimum);
+}
+
+NS_IMETHODIMP
+nsRangeFrame::AttributeChanged(int32_t  aNameSpaceID,
+                               nsIAtom* aAttribute,
+                               int32_t  aModType)
+{
+  NS_ASSERTION(mTrackDiv, "The track div must exist!");
+  NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
+
+  if (aNameSpaceID == kNameSpaceID_None &&
+      (aAttribute == nsGkAtoms::value ||
+       aAttribute == nsGkAtoms::min ||
+       aAttribute == nsGkAtoms::max ||
+       aAttribute == nsGkAtoms::step)) {
+    nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+    if (trackFrame) { // diplay:none?
+      PresContext()->PresShell()->FrameNeedsReflow(trackFrame,
+                                                   nsIPresShell::eResize,
+                                                   NS_FRAME_IS_DIRTY);
+    }
+
+    nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+    if (thumbFrame) { // diplay:none?
+      PresContext()->PresShell()->FrameNeedsReflow(thumbFrame,
+                                                   nsIPresShell::eResize,
+                                                   NS_FRAME_IS_DIRTY);
+    }
+  }
+
+  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+nsSize
+nsRangeFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext,
+                              nsSize aCBSize, nscoord aAvailableWidth,
+                              nsSize aMargin, nsSize aBorder,
+                              nsSize aPadding, bool aShrinkWrap)
+{
+  nscoord oneEm = NSToCoordRound(GetStyleFont()->mFont.size *
+                                 nsLayoutUtils::FontSizeInflationFor(this)); // 1em
+
+  // frameSizeOverride values just gets us to fall back to being horizontal
+  // (the actual values are irrelevant, as long as width > height):
+  nsSize frameSizeOverride(10,1);
+  bool isHorizontal = IsHorizontal(&frameSizeOverride);
+
+  nsSize autoSize;
+
+  // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+  // given too small a size when we're natively themed. If we're themed, we set
+  // our "thickness" dimension to zero below and rely on that
+  // GetMinimumWidgetSize check to correct that dimension to the natural
+  // thickness of a slider in the current theme.
+
+  if (isHorizontal) {
+    autoSize.width = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
+    autoSize.height = IsThemed() ? 0 : oneEm;
+  } else {
+    autoSize.width = IsThemed() ? 0 : oneEm;
+    autoSize.height = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
+  }
+
+  return autoSize;
+}
+
+nscoord
+nsRangeFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
+{
+  // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+  // given too small a size when we're natively themed. If we aren't native
+  // themed, we don't mind how small we're sized.
+  return nscoord(0);
+}
+
+nscoord
+nsRangeFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
+{
+  // frameSizeOverride values just gets us to fall back to being horizontal:
+  nsSize frameSizeOverride(10,1);
+  bool isHorizontal = IsHorizontal(&frameSizeOverride);
+
+  if (!isHorizontal && IsThemed()) {
+    // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+    // given too small a size when we're natively themed. We return zero and
+    // depend on that correction to get our "natuaral" width when we're a
+    // vertical slider.
+    return 0;
+  }
+
+  nscoord prefWidth = NSToCoordRound(GetStyleFont()->mFont.size *
+                                     nsLayoutUtils::FontSizeInflationFor(this)); // 1em
+
+  if (isHorizontal) {
+    prefWidth *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
+  }
+
+  return prefWidth;
+}
+
+bool
+nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const
+{
+  return true; // until we decide how to support vertical range (bug 840820)
+}
+
+nsIAtom*
+nsRangeFrame::GetType() const
+{
+  return nsGkAtoms::rangeFrame;
+}
+
+bool
+nsRangeFrame::ShouldUseNativeStyle() const
+{
+  return false; // TODO
+}
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsRangeFrame.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRangeFrame_h___
+#define nsRangeFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+class nsBaseContentList;
+
+class nsRangeFrame : public nsContainerFrame,
+                     public nsIAnonymousContentCreator
+{
+  friend nsIFrame*
+  NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+  nsRangeFrame(nsStyleContext* aContext);
+  virtual ~nsRangeFrame();
+
+public:
+  NS_DECL_QUERYFRAME_TARGET(nsRangeFrame)
+  NS_DECL_QUERYFRAME
+  NS_DECL_FRAMEARENA_HELPERS
+
+  // nsIFrame overrides
+  virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
+
+  void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                        const nsRect&           aDirtyRect,
+                        const nsDisplayListSet& aLists) MOZ_OVERRIDE;
+
+  NS_IMETHOD Reflow(nsPresContext*           aPresContext,
+                    nsHTMLReflowMetrics&     aDesiredSize,
+                    const nsHTMLReflowState& aReflowState,
+                    nsReflowStatus&          aStatus) MOZ_OVERRIDE;
+
+#ifdef DEBUG
+  NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE {
+    return MakeFrameName(NS_LITERAL_STRING("Range"), aResult);
+  }
+#endif
+
+  virtual bool IsLeaf() const MOZ_OVERRIDE { return true; }
+
+  // nsIAnonymousContentCreator
+  virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) MOZ_OVERRIDE;
+  virtual void AppendAnonymousContentTo(nsBaseContentList& aElements,
+                                        uint32_t aFilter) MOZ_OVERRIDE;
+
+  NS_IMETHOD AttributeChanged(int32_t  aNameSpaceID,
+                              nsIAtom* aAttribute,
+                              int32_t  aModType) MOZ_OVERRIDE;
+
+  virtual nsSize ComputeAutoSize(nsRenderingContext *aRenderingContext,
+                                 nsSize aCBSize, nscoord aAvailableWidth,
+                                 nsSize aMargin, nsSize aBorder,
+                                 nsSize aPadding, bool aShrinkWrap) MOZ_OVERRIDE;
+
+  virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
+  virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
+
+  virtual nsIAtom* GetType() const MOZ_OVERRIDE;
+
+  virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
+  {
+    return nsContainerFrame::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+  }
+
+  /**
+   * Returns true if the slider's thumb moves horizontally, or else false if it
+   * moves vertically.
+   *
+   * aOverrideFrameSize If specified, this will be used instead of the size of
+   *   the frame's rect (i.e. the frame's border-box size) if the frame's
+   *   rect would have otherwise been examined. This should only be specified
+   *   during reflow when the frame's [new] border-box size has not yet been
+   *   stored in its mRect.
+   */
+  bool IsHorizontal(const nsSize *aFrameSizeOverride = nullptr) const;
+
+  /**
+   * Returns whether the frame and its child should use the native style.
+   */
+  bool ShouldUseNativeStyle() const;
+
+private:
+
+  // Helper function which reflows the anonymous div frames.
+  nsresult ReflowAnonymousContent(nsPresContext*           aPresContext,
+                                  nsHTMLReflowMetrics&     aDesiredSize,
+                                  const nsHTMLReflowState& aReflowState);
+
+  /**
+   * Returns the input element's value as a fraction of the difference between
+   * the input's minimum and its maximum (i.e. returns 0.0 when the value is
+   * the same as the minimum, and returns 1.0 when the value is the same as the
+   * maximum).
+   */
+  double GetValueAsFractionOfRange();
+
+  /**
+   * The div used to show the track.
+   * @see nsRangeFrame::CreateAnonymousContent
+   */
+  nsCOMPtr<nsIContent> mTrackDiv;
+
+  /**
+   * The div used to show the thumb.
+   * @see nsRangeFrame::CreateAnonymousContent
+   */
+  nsCOMPtr<nsIContent> mThumbDiv;
+};
+
+#endif
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -108,16 +108,17 @@ FRAME_ID(nsMeterFrame)
 FRAME_ID(nsObjectFrame)
 FRAME_ID(nsPageBreakFrame)
 FRAME_ID(nsPageContentFrame)
 FRAME_ID(nsPageFrame)
 FRAME_ID(nsPlaceholderFrame)
 FRAME_ID(nsPopupSetFrame)
 FRAME_ID(nsProgressFrame)
 FRAME_ID(nsProgressMeterFrame)
+FRAME_ID(nsRangeFrame)
 FRAME_ID(nsResizerFrame)
 FRAME_ID(nsRootBoxFrame)
 FRAME_ID(nsScrollbarButtonFrame)
 FRAME_ID(nsScrollbarFrame)
 FRAME_ID(nsSelectsAreaFrame)
 FRAME_ID(nsSimplePageSequenceFrame)
 FRAME_ID(nsSliderFrame)
 FRAME_ID(nsSplittableFrame)
--- a/layout/generic/nsHTMLParts.h
+++ b/layout/generic/nsHTMLParts.h
@@ -184,16 +184,18 @@ NS_NewNativeSelectControlFrame(nsIPresSh
 nsIFrame*
 NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, uint32_t aFlags);
 nsIFrame*
 NS_NewProgressFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 // Table frame factories
 nsIFrame*
 NS_NewTableOuterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewTableCaptionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2128,16 +2128,19 @@ public:
    */
   bool IsBlockWrapper() const;
 
   /**
    * Get this frame's CSS containing block.
    *
    * The algorithm is defined in
    * http://www.w3.org/TR/CSS2/visudet.html#containing-block-details.
+   *
+   * NOTE: This is guaranteed to return a non-null pointer when invoked on any
+   * frame other than the root frame.
    */
   nsIFrame* GetContainingBlock() const;
 
   /**
    * Is this frame a containing block for floating elements?
    * Note that very few frames are, so default to false.
    */
   virtual bool IsFloatContainingBlock() const { return false; }
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -999,27 +999,26 @@ IsLineBreakingWhiteSpace(PRUnichar aChar
   // that needs to be analyzed for line breaking.
   return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
 }
 
 static bool
 TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
                                   bool aIsDoubleByte)
 {
-  uint32_t i;
   if (aIsDoubleByte) {
     const PRUnichar* chars = static_cast<const PRUnichar*>(aText);
-    for (i = 0; i < aLength; ++i) {
+    for (uint32_t i = 0; i < aLength; ++i) {
       if (IsLineBreakingWhiteSpace(chars[i]))
         return true;
     }
     return false;
   } else {
     const uint8_t* chars = static_cast<const uint8_t*>(aText);
-    for (i = 0; i < aLength; ++i) {
+    for (uint32_t i = 0; i < aLength; ++i) {
       if (IsLineBreakingWhiteSpace(chars[i]))
         return true;
     }
     return false;
   }
 }
 
 struct FrameTextTraversal {
@@ -1269,18 +1268,17 @@ BuildTextRuns(gfxContext* aContext, nsTe
       mayBeginInTextRun = false;
       break;
     }
 
     BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
       bool(seenTextRunBoundaryOnLaterLine), false, false };
     nsIFrame* child = line->mFirstChild;
     bool foundBoundary = false;
-    int32_t i;
-    for (i = line->GetChildCount() - 1; i >= 0; --i) {
+    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
       BuildTextRunsScanner::FindBoundaryResult result =
           scanner.FindBoundaries(child, &state);
       if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
         foundBoundary = true;
         break;
       } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
         break;
       }
@@ -1313,18 +1311,17 @@ BuildTextRuns(gfxContext* aContext, nsTe
   do {
     nsBlockFrame::line_iterator line = forwardIterator.GetLine();
     if (line->IsBlock())
       break;
     line->SetInvalidateTextRuns(false);
     scanner.SetAtStartOfLine();
     scanner.SetCommonAncestorWithLastFrame(nullptr);
     nsIFrame* child = line->mFirstChild;
-    int32_t i;
-    for (i = line->GetChildCount() - 1; i >= 0; --i) {
+    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
       scanner.ScanFrame(child);
       child = child->GetNextSibling();
     }
     if (line.get() == startLine.get()) {
       seenStartLine = true;
     }
     if (seenStartLine) {
       ++linesAfterStartLine;
@@ -1365,18 +1362,17 @@ bool BuildTextRunsScanner::IsTextRunVali
   if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW)
     return mMappedFlows.Length() == 1 &&
       mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) &&
       mMappedFlows[0].mEndFrame == nullptr;
 
   TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
   if (userData->mMappedFlowCount != mMappedFlows.Length())
     return false;
-  uint32_t i;
-  for (i = 0; i < mMappedFlows.Length(); ++i) {
+  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
         int32_t(userData->mMappedFlows[i].mContentLength) !=
             mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
       return false;
   }
   return true;
 }
 
@@ -1435,27 +1431,26 @@ void BuildTextRunsScanner::FlushLineBrea
   nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
   // textRun may be null for various reasons, including because we constructed
   // a partial textrun just to get the linebreaker and other state set up
   // to build the next textrun.
   if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
     aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
   }
 
-  uint32_t i;
-  for (i = 0; i < mBreakSinks.Length(); ++i) {
+  for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
     if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
       // TODO cause frames associated with the textrun to be reflowed, if they
       // aren't being reflowed already!
     }
     mBreakSinks[i]->Finish();
   }
   mBreakSinks.Clear();
 
-  for (i = 0; i < mTextRunsToDelete.Length(); ++i) {
+  for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) {
     gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
     gTextRuns->RemoveFromCache(deleteTextRun);
     delete deleteTextRun;
   }
   mTextRunsToDelete.Clear();
 }
 
 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
@@ -1836,21 +1831,20 @@ BuildTextRunsScanner::BuildTextRunForFra
     case NS_STYLE_WORDBREAK_KEEP_ALL:
       mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll);
       break;
     default:
       mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal);
       break;
   }
 
-  uint32_t i;
   const nsStyleText* textStyle = nullptr;
   const nsStyleFont* fontStyle = nullptr;
   nsStyleContext* lastStyleContext = nullptr;
-  for (i = 0; i < mMappedFlows.Length(); ++i) {
+  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     nsTextFrame* f = mappedFlow->mStartFrame;
 
     lastStyleContext = f->StyleContext();
     // Detect use of text-transform or font-variant anywhere in the run
     textStyle = f->GetStyleText();
     if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
       anyTextTransformStyle = true;
@@ -1983,17 +1977,17 @@ BuildTextRunsScanner::BuildTextRunForFra
 
   gfxSkipChars skipChars;
   skipChars.TakeFrom(&builder);
   // Convert linebreak coordinates to transformed string offsets
   NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
                "Didn't find all the frames to break-before...");
   gfxSkipCharsIterator iter(skipChars);
   nsAutoTArray<uint32_t,50> textBreakPointsAfterTransform;
-  for (i = 0; i < textBreakPoints.Length(); ++i) {
+  for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
     nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, 
             iter.ConvertOriginalToSkipped(textBreakPoints[i]));
   }
   if (mStartOfLine) {
     nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
                                             transformedLength);
   }
 
@@ -2004,17 +1998,17 @@ BuildTextRunsScanner::BuildTextRunForFra
   }
   if (anyTextTransformStyle) {
     transformingFactory =
       new nsCaseTransformTextRunFactory(transformingFactory.forget());
   }
   nsTArray<nsStyleContext*> styles;
   if (transformingFactory) {
     iter.SetOriginalOffset(0);
-    for (i = 0; i < mMappedFlows.Length(); ++i) {
+    for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
       MappedFlow* mappedFlow = &mMappedFlows[i];
       nsTextFrame* f;
       for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
            f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
         uint32_t offset = iter.GetSkippedOffset();
         iter.AdvanceOriginal(f->GetContentLength());
         uint32_t end = iter.GetSkippedOffset();
         nsStyleContext* sc = f->StyleContext();
@@ -2141,19 +2135,18 @@ BuildTextRunsScanner::SetupLineBreakerCo
     userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
   }
   userData->mMappedFlowCount = mMappedFlows.Length();
   userData->mLastFlowIndex = 0;
 
   uint32_t nextBreakIndex = 0;
   nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
 
-  uint32_t i;
   const nsStyleText* textStyle = nullptr;
-  for (i = 0; i < mMappedFlows.Length(); ++i) {
+  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     nsTextFrame* f = mappedFlow->mStartFrame;
 
     textStyle = f->GetStyleText();
     nsTextFrameUtils::CompressionMode compression =
       CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
 
     // Figure out what content is included in this flow.
@@ -2257,18 +2250,17 @@ BuildTextRunsScanner::SetupBreakSinksFor
   // explicitly.
   nsIAtom* hyphenationLanguage =
     styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr;
   // We keep this pointed at the skip-chars data for the current mappedFlow.
   // This lets us cheaply check whether the flow has compressed initial
   // whitespace...
   gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
 
-  uint32_t i;
-  for (i = 0; i < mMappedFlows.Length(); ++i) {
+  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     uint32_t offset = iter.GetSkippedOffset();
     gfxSkipCharsIterator iterNext = iter;
     iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
             mappedFlow->mStartFrame->GetContentOffset());
 
     nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
       new BreakSink(aTextRun, mContext, offset,
@@ -2365,18 +2357,17 @@ FindFlowForContent(TextRunUserData* aUse
   }
 
   return nullptr;
 }
 
 void
 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
 {
-  uint32_t i;
-  for (i = 0; i < mMappedFlows.Length(); ++i) {
+  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     nsTextFrame* startFrame = mappedFlow->mStartFrame;
     nsTextFrame* endFrame = mappedFlow->mEndFrame;
     nsTextFrame* f;
     for (f = startFrame; f != endFrame;
          f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
 #ifdef DEBUG_roc
       if (f->GetTextRun(mWhichTextRun)) {
@@ -2429,18 +2420,18 @@ BuildTextRunsScanner::AssignTextRun(gfxT
           uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
           clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
         }
         f->ClearTextRun(clearFrom, mWhichTextRun);
 
 #ifdef DEBUG
         if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
           // oldTextRun was destroyed - assert that we don't reference it.
-          for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
-            NS_ASSERTION(oldTextRun != mBreakSinks[i]->mTextRun,
+          for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
+            NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
                          "destroyed text run is still in use");
           }
         }
 #endif
       }
       f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
     }
     // Set this bit now; we can't set it any earlier because
@@ -2834,18 +2825,17 @@ PropertyProvider::ComputeJustifiableChar
 {
   // Scan non-skipped characters and count justifiable chars.
   nsSkipCharsRunIterator
     run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
   run.SetOriginalOffset(aOffset);
   uint32_t justifiableChars = 0;
   bool isCJK = IsChineseOrJapanese(mFrame);
   while (run.NextRun()) {
-    int32_t i;
-    for (i = 0; i < run.GetRunLength(); ++i) {
+    for (int32_t i = 0; i < run.GetRunLength(); ++i) {
       justifiableChars +=
         IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK);
     }
   }
   return justifiableChars;
 }
 
 /**
@@ -2918,19 +2908,18 @@ PropertyProvider::GetSpacingInternal(uin
 
   // First, compute the word and letter spacing
   if (mWordSpacing || mLetterSpacing) {
     // Iterate over non-skipped characters
     nsSkipCharsRunIterator
       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
     while (run.NextRun()) {
       uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
-      int32_t i;
       gfxSkipCharsIterator iter = run.GetPos();
-      for (i = 0; i < run.GetRunLength(); ++i) {
+      for (int32_t i = 0; i < run.GetRunLength(); ++i) {
         if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
           // End of a cluster, not in a ligature: put letter-spacing after it
           aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
         }
         if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
                                   mTextStyle)) {
           // It kinda sucks, but space characters can be part of clusters,
           // and even still be whitespace (I think!)
@@ -2963,20 +2952,19 @@ PropertyProvider::GetSpacingInternal(uin
     // justification space on either side of the cluster
     bool isCJK = IsChineseOrJapanese(mFrame);
     gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart);
     FindJustificationRange(&justificationStart, &justificationEnd);
 
     nsSkipCharsRunIterator
       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
     while (run.NextRun()) {
-      int32_t i;
       gfxSkipCharsIterator iter = run.GetPos();
       int32_t runOriginalOffset = run.GetOriginalOffset();
-      for (i = 0; i < run.GetRunLength(); ++i) {
+      for (int32_t i = 0; i < run.GetRunLength(); ++i) {
         int32_t iterOriginalOffset = runOriginalOffset + i;
         if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) {
           iter.SetOriginalOffset(iterOriginalOffset);
           FindClusterStart(mTextRun, runOriginalOffset, &iter);
           uint32_t clusterFirstChar = iter.GetSkippedOffset();
           FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter);
           uint32_t clusterLastChar = iter.GetSkippedOffset();
           // Only apply justification to characters before justificationEnd
@@ -3393,18 +3381,18 @@ NS_IMETHODIMP nsBlinkTimer::Notify(nsITi
   char buf[50];
   PRTime delta;
   delta = now - gLastTick;
   gLastTick = now;
   PR_snprintf(buf, sizeof(buf), "%lldusec", delta);
   printf("%s\n", buf);
 #endif
 
-  uint32_t i, n = mFrames.Length();
-  for (i = 0; i < n; i++) {
+  uint32_t n = mFrames.Length();
+  for (uint32_t i = 0; i < n; i++) {
     FrameData& frameData = mFrames.ElementAt(i);
 
     // Determine damaged area and tell view manager to redraw it
     // blink doesn't blink outline ... I hope
     frameData.mFrame->InvalidateFrame();
   }
   return NS_OK;
 }
@@ -5692,23 +5680,23 @@ nsTextFrame::PaintTextWithSelection(gfxC
   SelectionType allTypes;
   if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
                                     aProvider, aContentOffset, aContentLength,
                                     aTextPaintStyle, details, &allTypes,
                                     aClipEdges, aCallbacks)) {
     DestroySelectionDetails(details);
     return false;
   }
-  int32_t i;
   // Iterate through just the selection types that paint decorations and
   // paint decorations for any that actually occur in this frame. Paint
   // higher-numbered selection types below lower-numered ones on the
   // general principal that lower-numbered selections are higher priority.
   allTypes &= SelectionTypesWithDecorations;
-  for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) {
+  for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1;
+       i >= 1; --i) {
     SelectionType type = 1 << (i - 1);
     if (allTypes & type) {
       // There is some selection of this type. Try to paint its decorations
       // (there might not be any for this type but that's OK,
       // PaintTextSelectionDecorations will exit early).
       PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
                                     aProvider, aContentOffset, aContentLength,
                                     aTextPaintStyle, details, type,
@@ -6210,18 +6198,17 @@ nsTextFrame::IsVisibleInSelection(nsISel
  */
 static uint32_t
 CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength,
               gfxFloat aWidth, PropertyProvider* aProvider,
               gfxFloat* aFitWidth)
 {
   uint32_t last = 0;
   gfxFloat width = 0;
-  uint32_t i;
-  for (i = 1; i <= aLength; ++i) {
+  for (uint32_t i = 1; i <= aLength; ++i) {
     if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
       gfxFloat nextWidth = width +
           aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
       if (nextWidth > aWidth)
         break;
       last = i;
       width = nextWidth;
     }
@@ -6786,18 +6773,17 @@ ClusterIterator::ClusterIterator(nsTextF
       mWordBreaks[textLen] = true;
     }
     textStart = 0;
     nsAutoString str;
     mFrag->AppendTo(str, textOffset, textLen);
     aContext.Insert(str, 0);
   }
   nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
-  int32_t i;
-  for (i = 0; i <= textLen; ++i) {
+  for (int32_t i = 0; i <= textLen; ++i) {
     int32_t indexInText = i + textStart;
     mWordBreaks[i] |=
       wordBreaker->BreakInBetween(aContext.get(), indexInText,
                                   aContext.get() + indexInText,
                                   aContext.Length() - indexInText);
   }
 }
 
@@ -8257,18 +8243,18 @@ nsTextFrame::TrimTrailingWhiteSpace(nsRe
       (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
     // Check if any character in the last cluster is justifiable
     PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
                               nullptr, 0, nsTextFrame::eInflated);
     bool isCJK = IsChineseOrJapanese(this);
     gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter);
     provider.FindJustificationRange(&justificationStart, &justificationEnd);
 
-    int32_t i;
-    for (i = justificationEnd.GetOriginalOffset(); i < trimmed.GetEnd(); ++i) {
+    for (int32_t i = justificationEnd.GetOriginalOffset();
+         i < trimmed.GetEnd(); ++i) {
       if (IsJustifiableCharacter(frag, i, isCJK)) {
         result.mLastCharIsJustifiable = true;
       }
     }
   }
 
   gfxFloat advanceDelta;
   mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
--- a/layout/reftests/flexbox/reftest.list
+++ b/layout/reftests/flexbox/reftest.list
@@ -1,25 +1,36 @@
-# Check that flexbox pref is default-enabled:
-== flexbox-pref-1.xhtml flexbox-pref-1-enabled-ref.xhtml
-
+# Tests for flexbox pref "layout.css.flexbox.enabled"
+# (Note that it defaults to being off in release builds - see bug 841876)
 # Check that manually setting the pref on/off w/ test-pref() works correctly:
 test-pref(layout.css.flexbox.enabled,false) == flexbox-pref-1.xhtml flexbox-pref-1-disabled-ref.xhtml
 test-pref(layout.css.flexbox.enabled,true) == flexbox-pref-1.xhtml flexbox-pref-1-enabled-ref.xhtml
 
+# Enable pref for remaining tests
+# (Most tests only need it in the testcase, but a few use it in the
+# reference case, so we'll just enable it using "pref()" to make
+# it available for both.)
+default-preferences pref(layout.css.flexbox.enabled,true)
+
 # Tests for cross-axis alignment (align-self / align-items properties)
 == flexbox-align-self-baseline-horiz-1.xhtml  flexbox-align-self-baseline-horiz-1-ref.xhtml
 fails == flexbox-align-self-baseline-horiz-2.xhtml  flexbox-align-self-baseline-horiz-2-ref.xhtml # bug 793456, and possibly others
 # This one fails on windows R (but not Ru, strangely). On Windows R, the
 # single-line <label> flex item has a different background size in test vs. ref
 fuzzy-if(B2G,10,3) random-if(winWidget) == flexbox-align-self-baseline-horiz-3.xhtml  flexbox-align-self-baseline-horiz-3-ref.xhtml # XXXdholbert investigate
-== flexbox-align-self-baseline-horiz-4.xhtml flexbox-align-self-baseline-horiz-4-ref.xhtml
+# XXXdholbert This test needs the flexbox pref to be enabled _at startup time_
+# (when we parse ua.css) in order to pass -- so it'll fail (only in release
+# builds) until we enable the flexbox pref in those builds in bug 841876:
+random == flexbox-align-self-baseline-horiz-4.xhtml flexbox-align-self-baseline-horiz-4-ref.xhtml
 
 == flexbox-align-self-horiz-1-block.xhtml  flexbox-align-self-horiz-1-ref.xhtml
-== flexbox-align-self-horiz-1-table.xhtml  flexbox-align-self-horiz-1-ref.xhtml
+# XXXdholbert This test needs the flexbox pref to be enabled _at startup time_
+# (when we parse ua.css) in order to pass -- so it'll fail (in release builds)
+# until we enable the flexbox pref in those builds in bug 841876:
+random == flexbox-align-self-horiz-1-table.xhtml  flexbox-align-self-horiz-1-ref.xhtml
 == flexbox-align-self-horiz-2.xhtml  flexbox-align-self-horiz-2-ref.xhtml
 == flexbox-align-self-horiz-3.xhtml  flexbox-align-self-horiz-3-ref.xhtml
 == flexbox-align-self-horiz-4.xhtml  flexbox-align-self-horiz-4-ref.xhtml
 == flexbox-align-self-horiz-5.xhtml  flexbox-align-self-horiz-5-ref.xhtml
 == flexbox-align-self-vert-1.xhtml  flexbox-align-self-vert-1-ref.xhtml
 == flexbox-align-self-vert-2.xhtml  flexbox-align-self-vert-2-ref.xhtml
 == flexbox-align-self-vert-3.xhtml  flexbox-align-self-vert-3-ref.xhtml
 == flexbox-align-self-vert-4.xhtml  flexbox-align-self-vert-4-ref.xhtml
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-from-range-to-other-type-unthemed-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="checkbox" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-from-range-to-other-type-unthemed-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when switching to another type, the input element should look
+             like that type (not like an input range element) -->
+  <script type="text/javascript">
+    function setToCheckbox()
+    {
+      document.getElementById('i').type='checkbox';
+    }
+    function disableReftestWait()
+    {
+      document.documentElement.className = '';
+    }
+  </script>
+
+  <body onload="setToCheckbox(); disableReftestWait();">
+    <input type='range' id='i' style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-different-fraction-of-range-unthemed-1-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="range" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-different-fraction-of-range-unthemed-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <!-- Test: that range with value=70 is different to the default value=50. -->
+  <body>
+    <input type='range' style="-moz-appearance:none;" value=90 min=20 max=120>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-not-other-type-unthemed-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="range" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-not-other-type-unthemed-1a-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="text" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-not-other-type-unthemed-1b-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="text" value="50" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-not-other-type-unthemed-1c-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="checkbox" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-same-fraction-of-range-unthemed-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="range" value=70 style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-range-same-fraction-of-range-unthemed-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <!-- Test: that range with value=90,min=90,max=120 looks the same as range
+       with value=70 (also tests that it doesn't look like type=text, since the
+       text displayed would be different in the type=text case). -->
+  <body>
+    <input type='range' style="-moz-appearance:none;" value=90 min=20 max=120>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-to-range-from-other-type-unthemed-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="range" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/input-to-range-from-other-type-unthemed-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: input element changed to range state doesn't look like checkbox state -->
+  <script type="text/javascript">
+    function setToRange()
+    {
+      document.getElementById('i').type='range';
+    }
+    function disableReftestWait()
+    {
+      document.documentElement.className = '';
+    }
+  </script>
+
+  <body onload="setToRange(); disableReftestWait();">
+    <input type='checkbox' id='i' style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/range/reftest.list
@@ -0,0 +1,16 @@
+
+default-preferences pref(dom.experimental_forms_range,true)
+
+# sanity checks:
+!= input-range-not-other-type-unthemed-1.html input-range-not-other-type-unthemed-1a-notref.html
+!= input-range-not-other-type-unthemed-1.html input-range-not-other-type-unthemed-1b-notref.html
+!= input-range-not-other-type-unthemed-1.html input-range-not-other-type-unthemed-1c-notref.html
+
+# dynamic type changes:
+== input-to-range-from-other-type-unthemed-1.html input-to-range-from-other-type-unthemed-1-ref.html
+== input-from-range-to-other-type-unthemed-1.html input-from-range-to-other-type-unthemed-1-ref.html
+
+# for different values:
+!= input-range-different-fraction-of-range-unthemed-1.html input-range-different-fraction-of-range-unthemed-1-notref.html
+== input-range-same-fraction-of-range-unthemed-1.html input-range-same-fraction-of-range-unthemed-1-ref.html
+
--- a/layout/reftests/forms/input/reftest.list
+++ b/layout/reftests/forms/input/reftest.list
@@ -1,4 +1,6 @@
 include email/reftest.list
 include tel/reftest.list
 include search/reftest.list
 include url/reftest.list
+include range/reftest.list
+
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -363,16 +363,21 @@ select:disabled:disabled /* Need the pse
 {
   -moz-user-input: disabled;
   -moz-user-focus: ignore;
   color: GrayText;
   background-color: ThreeDFace;
   cursor: inherit;
 }
 
+input:disabled > .anonymous-div,
+textarea:disabled > .anonymous-div {
+  cursor: default;
+}
+
 option:disabled,
 optgroup:disabled {
   background-color: transparent;
 }
 
 /* hidden inputs */
 input[type="hidden"] {
   -moz-appearance: none;
@@ -659,17 +664,17 @@ progress {
   -moz-border-top-colors: ThreeDShadow #e6e6e6;
   -moz-border-right-colors: ThreeDHighlight #e6e6e6;
   -moz-border-bottom-colors: ThreeDHighlight #e6e6e6;
   -moz-border-left-colors: ThreeDShadow #e6e6e6;
   background-color: #e6e6e6;
 }
 
 ::-moz-progress-bar {
-  /* Block styles that would change the type of frame we construct. */
+  /* Prevent styling that would change the type of frame we construct. */
   display: inline-block ! important;
   float: none ! important;
   position: static ! important;
   overflow: visible ! important;
   -moz-box-sizing: border-box ! important;
 
   -moz-appearance: progresschunk;
   height: 100%;
@@ -707,16 +712,67 @@ meter {
   /* orange. */
   background: -moz-linear-gradient(top, #fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%);
 }
 :-moz-meter-sub-sub-optimum::-moz-meter-bar {
   /* red. */
   background: -moz-linear-gradient(top, #f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
 }
 
+input[type="range"] {
+  -moz-appearance: none;
+  display: inline-block !important;
+  cursor: default;
+  width: 12em;
+  height: 1.3em;
+  background: none;
+  border: none;
+  margin: 0 0.7em;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling the thumb without worrying about the
+ * logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored.
+ *
+ * If content authors want to have a vertical range, they will also need to
+ * set the width/height of this pseudo-element.
+ */
+::-moz-range-track {
+  /* Prevent styling that would change the type of frame we construct. */
+  display: inline-block !important;
+  float: none !important;
+  position: static !important;
+  border: none;
+  border-top: solid 0.1em lightgrey;
+  border-bottom: solid 0.1em lightgrey;
+  background-color: grey;
+  width: 100%;
+  height: 0.2em;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling the thumb without worrying about the
+ * logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored.
+ */
+::-moz-range-thumb {
+  /* Prevent styling that would change the type of frame we construct. */
+  display: inline-block !important;
+  float: none !important;
+  position: static !important;
+  width: 1em;
+  height: 1em;
+  border: 0.1em solid grey;
+  border-radius: 0.5em;
+  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><linearGradient id='g' x2='0' y2='100%'><stop stop-color='%23ddd'/><stop offset='100%' stop-color='white'/></linearGradient><rect fill='url(%23g)' width='100%' height='100%'/></svg>");
+}
+
 %ifdef XP_OS2
 input {
   font: medium serif; font-family: inherit
 }
 
 select {
   font: medium serif; font-family: inherit
 }
--- a/layout/style/nsCSSPseudoElementList.h
+++ b/layout/style/nsCSSPseudoElementList.h
@@ -48,10 +48,12 @@ CSS_PSEUDO_ELEMENT(mozFocusOuter, ":-moz
 CSS_PSEUDO_ELEMENT(mozListBullet, ":-moz-list-bullet", 0)
 CSS_PSEUDO_ELEMENT(mozListNumber, ":-moz-list-number", 0)
 
 CSS_PSEUDO_ELEMENT(mozMathStretchy, ":-moz-math-stretchy", 0)
 CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous", 0)
 
 // HTML5 Forms pseudo elements
 CSS_PSEUDO_ELEMENT(mozProgressBar, ":-moz-progress-bar", 0)
+CSS_PSEUDO_ELEMENT(mozRangeTrack, ":-moz-range-track", 0)
+CSS_PSEUDO_ELEMENT(mozRangeThumb, ":-moz-range-thumb", 0)
 CSS_PSEUDO_ELEMENT(mozMeterBar, ":-moz-meter-bar", 0)
 CSS_PSEUDO_ELEMENT(mozPlaceholder, ":-moz-placeholder", 0)
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -83,24 +83,16 @@ NS_NewComputedDOMStyle(dom::Element* aEl
 
     computedStyle = new nsComputedDOMStyle(aElement, aPseudoElt, aPresShell,
                                            aStyleType);
   }
 
   return computedStyle.forget();
 }
 
-static nsIFrame*
-GetContainingBlockFor(nsIFrame* aFrame) {
-  if (!aFrame) {
-    return nullptr;
-  }
-  return aFrame->GetContainingBlock();
-}
-
 nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
                                        const nsAString& aPseudoElt,
                                        nsIPresShell* aPresShell,
                                        StyleType aStyleType)
   : mDocumentWeak(nullptr), mOuterFrame(nullptr),
     mInnerFrame(nullptr), mPresShell(nullptr),
     mStyleType(aStyleType),
     mExposeVisitedStyle(false)
@@ -3531,70 +3523,66 @@ nsComputedDOMStyle::GetOffsetWidthFor(mo
       NS_ERROR("Invalid position");
       return nullptr;
   }
 }
 
 CSSValue*
 nsComputedDOMStyle::GetAbsoluteOffset(mozilla::css::Side aSide)
 {
-  nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
-
-  nsIFrame* container = GetContainingBlockFor(mOuterFrame);
-  if (container) {
-    nsMargin margin = mOuterFrame->GetUsedMargin();
-    nsMargin border = container->GetUsedBorder();
-    nsMargin scrollbarSizes(0, 0, 0, 0);
-    nsRect rect = mOuterFrame->GetRect();
-    nsRect containerRect = container->GetRect();
-
-    if (container->GetType() == nsGkAtoms::viewportFrame) {
-      // For absolutely positioned frames scrollbars are taken into
-      // account by virtue of getting a containing block that does
-      // _not_ include the scrollbars.  For fixed positioned frames,
-      // the containing block is the viewport, which _does_ include
-      // scrollbars.  We have to do some extra work.
-      // the first child in the default frame list is what we want
-      nsIFrame* scrollingChild = container->GetFirstPrincipalChild();
-      nsIScrollableFrame *scrollFrame = do_QueryFrame(scrollingChild);
-      if (scrollFrame) {
-        scrollbarSizes = scrollFrame->GetActualScrollbarSizes();
-      }
+  MOZ_ASSERT(mOuterFrame, "need a frame, so we can call GetContainingBlock()");
+
+  nsIFrame* container = mOuterFrame->GetContainingBlock();
+  nsMargin margin = mOuterFrame->GetUsedMargin();
+  nsMargin border = container->GetUsedBorder();
+  nsMargin scrollbarSizes(0, 0, 0, 0);
+  nsRect rect = mOuterFrame->GetRect();
+  nsRect containerRect = container->GetRect();
+
+  if (container->GetType() == nsGkAtoms::viewportFrame) {
+    // For absolutely positioned frames scrollbars are taken into
+    // account by virtue of getting a containing block that does
+    // _not_ include the scrollbars.  For fixed positioned frames,
+    // the containing block is the viewport, which _does_ include
+    // scrollbars.  We have to do some extra work.
+    // the first child in the default frame list is what we want
+    nsIFrame* scrollingChild = container->GetFirstPrincipalChild();
+    nsIScrollableFrame *scrollFrame = do_QueryFrame(scrollingChild);
+    if (scrollFrame) {
+      scrollbarSizes = scrollFrame->GetActualScrollbarSizes();
     }
-
-    nscoord offset = 0;
-    switch (aSide) {
-      case NS_SIDE_TOP:
-        offset = rect.y - margin.top - border.top - scrollbarSizes.top;
-
-        break;
-      case NS_SIDE_RIGHT:
-        offset = containerRect.width - rect.width -
-          rect.x - margin.right - border.right - scrollbarSizes.right;
-
-        break;
-      case NS_SIDE_BOTTOM:
-        offset = containerRect.height - rect.height -
-          rect.y - margin.bottom - border.bottom - scrollbarSizes.bottom;
-
-        break;
-      case NS_SIDE_LEFT:
-        offset = rect.x - margin.left - border.left - scrollbarSizes.left;
-
-        break;
-      default:
-        NS_ERROR("Invalid side");
-        break;
-    }
-    val->SetAppUnits(offset);
-  } else {
-    // XXX no frame.  This property makes no sense
-    val->SetAppUnits(0);
   }
 
+  nscoord offset = 0;
+  switch (aSide) {
+    case NS_SIDE_TOP:
+      offset = rect.y - margin.top - border.top - scrollbarSizes.top;
+
+      break;
+    case NS_SIDE_RIGHT:
+      offset = containerRect.width - rect.width -
+        rect.x - margin.right - border.right - scrollbarSizes.right;
+
+      break;
+    case NS_SIDE_BOTTOM:
+      offset = containerRect.height - rect.height -
+        rect.y - margin.bottom - border.bottom - scrollbarSizes.bottom;
+
+      break;
+    case NS_SIDE_LEFT:
+      offset = rect.x - margin.left - border.left - scrollbarSizes.left;
+
+      break;
+    default:
+      NS_ERROR("Invalid side");
+      break;
+  }
+
+  nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
+  val->SetAppUnits(offset);
   return val;
 }
 
 MOZ_STATIC_ASSERT(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 &&
                   NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3,
                   "box side constants not as expected for NS_OPPOSITE_SIDE");
 #define NS_OPPOSITE_SIDE(s_) mozilla::css::Side(((s_) + 2) & 3)
 
@@ -3900,41 +3888,33 @@ nsComputedDOMStyle::StyleCoordToNSCoord(
 
 bool
 nsComputedDOMStyle::GetCBContentWidth(nscoord& aWidth)
 {
   if (!mOuterFrame) {
     return false;
   }
 
-  nsIFrame* container = GetContainingBlockFor(mOuterFrame);
-  if (!container) {
-    return false;
-  }
-
   AssertFlushedPendingReflows();
 
+  nsIFrame* container = mOuterFrame->GetContainingBlock();
   aWidth = container->GetContentRect().width;
   return true;
 }
 
 bool
 nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight)
 {
   if (!mOuterFrame) {
     return false;
   }
 
-  nsIFrame* container = GetContainingBlockFor(mOuterFrame);
-  if (!container) {
-    return false;
-  }
-
   AssertFlushedPendingReflows();
 
+  nsIFrame* container = mOuterFrame->GetContainingBlock();
   aHeight = container->GetContentRect().height;
   return true;
 }
 
 bool
 nsComputedDOMStyle::GetFrameBorderRectWidth(nscoord& aWidth)
 {
   if (!mInnerFrame) {
--- a/layout/style/test/test_flexbox_align_self_auto.html
+++ b/layout/style/test/test_flexbox_align_self_auto.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_align_self_auto.html") for the actual
  * test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src = "file_flexbox_align_self_auto.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
 </pre>
--- a/layout/style/test/test_flexbox_child_display_values.html
+++ b/layout/style/test/test_flexbox_child_display_values.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_child_display_values.xhtml") for the
  * actual test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src =
   "file_flexbox_child_display_values.xhtml";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
--- a/layout/style/test/test_flexbox_flex_grow_and_shrink.html
+++ b/layout/style/test/test_flexbox_flex_grow_and_shrink.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_flex_grow_and_shrink.html") for
  * the actual test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src =
   "file_flexbox_flex_grow_and_shrink.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
--- a/layout/style/test/test_flexbox_flex_shorthand.html
+++ b/layout/style/test/test_flexbox_flex_shorthand.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_flex_shorthand.html") for the actual
  * test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src = "file_flexbox_flex_shorthand.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
 </pre>
--- a/layout/style/test/test_flexbox_layout.html
+++ b/layout/style/test/test_flexbox_layout.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_layout.html") for the actual
  * test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src = "file_flexbox_layout.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
 </pre>
--- a/layout/style/test/test_flexbox_min_size_auto.html
+++ b/layout/style/test/test_flexbox_min_size_auto.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_min_size_auto.html") for the actual
  * test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src = "file_flexbox_min_size_auto.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
 </pre>
--- a/layout/style/test/test_flexbox_order.html
+++ b/layout/style/test/test_flexbox_order.html
@@ -25,19 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
  * flexbox about:config pref before its document is instantiated.
  *
  * See the iframe's source ("file_flexbox_order.html") for the actual
  * test code and for more documentation.
  */
 
 SimpleTest.waitForExplicitFinish();
 
-ok(SpecialPowers.getBoolPref("layout.css.flexbox.enabled"),
-   "expecting flexbox pref to be enabled by default");
-
+SpecialPowers.setBoolPref("layout.css.flexbox.enabled", true);
 document.getElementById("iframe").src = "file_flexbox_order.html";
 
 function finish() {
   SpecialPowers.clearUserPref("layout.css.flexbox.enabled");
   SimpleTest.finish();
 }
 </script>
 </pre>
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1678,17 +1678,21 @@ pref("layout.css.masking.enabled", true)
 // Is support for the the @supports rule enabled?
 #ifdef RELEASE_BUILD
 pref("layout.css.supports-rule.enabled", false);
 #else
 pref("layout.css.supports-rule.enabled", true);
 #endif
 
 // Is support for CSS Flexbox enabled?
+#ifdef RELEASE_BUILD
+pref("layout.css.flexbox.enabled", false);
+#else
 pref("layout.css.flexbox.enabled", true);
+#endif
 
 // Are sets of prefixed properties supported?
 pref("layout.css.prefixes.border-image", true);
 pref("layout.css.prefixes.transforms", true);
 pref("layout.css.prefixes.transitions", true);
 pref("layout.css.prefixes.animations", true);
 
 // Is support for the :scope selector enabled?
--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -179,162 +179,55 @@ nsDiskCacheInputStream::ReadSegments(nsW
 NS_IMETHODIMP
 nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
 {
     *nonBlocking = false;
     return NS_OK;
 }
 
 
-/******************************************************************************
- *  nsDiskCacheOutputStream
- *****************************************************************************/
-class nsDiskCacheOutputStream : public nsIOutputStream
-{
-public:
-    nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
-    virtual ~nsDiskCacheOutputStream();
-
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOUTPUTSTREAM
-
-    void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
-
-private:
-    nsDiskCacheStreamIO *           mStreamIO;  // backpointer to parent
-    bool                            mClosed;
-};
-
-
-NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheOutputStream,
-                              nsIOutputStream)
-
-nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
-    : mStreamIO(parent)
-    , mClosed(false)
-{
-    NS_ADDREF(mStreamIO);
-}
-
-
-nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
-{
-    Close();
-    ReleaseStreamIO();
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::Close()
-{
-    nsresult rv = NS_OK;
-    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
-
-    if (!mClosed) {
-        mClosed = true;
-        // tell parent streamIO we are closing
-        rv = mStreamIO->CloseOutputStream(this);
-    }
-
-    mozilla::Telemetry::ID id;
-    if (NS_IsMainThread())
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
-    else
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
-
-    mozilla::Telemetry::AccumulateTimeDelta(id, start);
-
-    return rv;
-}
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::Flush()
-{
-    if (mClosed)  return NS_BASE_STREAM_CLOSED;
-    // yeah, yeah, well get to it...eventually...
-    return NS_OK;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::Write(const char *buf, uint32_t count, uint32_t *bytesWritten)
-{
-    if (mClosed)  return NS_BASE_STREAM_CLOSED;
-    return mStreamIO->Write(buf, count, bytesWritten);
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
-{
-    NS_NOTREACHED("WriteFrom");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
-                                        void *           closure,
-                                        uint32_t         count,
-                                        uint32_t *       bytesWritten)
-{
-    NS_NOTREACHED("WriteSegments");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
-{
-    *nonBlocking = false;
-    return NS_OK;
-}
-
 
 
 /******************************************************************************
  *  nsDiskCacheStreamIO
  *****************************************************************************/
-NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheStreamIO, nsIOutputStream)
 
 nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding *   binding)
     : mBinding(binding)
-    , mOutStream(nullptr)
     , mInStreamCount(0)
     , mFD(nullptr)
     , mStreamEnd(0)
     , mBufSize(0)
     , mBuffer(nullptr)
+    , mOutputStreamIsOpen(false)
 {
     mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
 
     // acquire "death grip" on cache service
     nsCacheService *service = nsCacheService::GlobalInstance();
     NS_ADDREF(service);
 }
 
 
 nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
 {
-    Close();
+    nsCacheService::AssertOwnsLock();
+
+    // Close the outputstream
+    if (mBinding && mOutputStreamIsOpen) {
+        (void)CloseOutputStream();
+    }
 
     // release "death grip" on cache service
     nsCacheService *service = nsCacheService::GlobalInstance();
     NS_RELEASE(service);
-}
 
-
-void
-nsDiskCacheStreamIO::Close()
-{
-    // this should only be called from our destructor
-    // no one is interested in us anymore, so we don't need to grab any locks
-    
     // assert streams closed
-    NS_ASSERTION(!mOutStream, "output stream still open");
+    NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
     NS_ASSERTION(mInStreamCount == 0, "input stream still open");
     NS_ASSERTION(!mFD, "file descriptor not closed");
 
     DeleteBuffer();
 }
 
 
 // NOTE: called with service lock held
@@ -343,17 +236,17 @@ nsDiskCacheStreamIO::GetInputStream(uint
 {
     NS_ENSURE_ARG_POINTER(inputStream);
     NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
 
     *inputStream = nullptr;
     
     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
 
-    if (mOutStream) {
+    if (mOutputStreamIsOpen) {
         NS_WARNING("already have an output stream open");
         return NS_ERROR_NOT_AVAILABLE;
     }
 
     nsresult            rv;
     PRFileDesc *        fd = nullptr;
 
     mStreamEnd = mBinding->mCacheEntry->DataSize();
@@ -389,77 +282,81 @@ nsDiskCacheStreamIO::GetInputStream(uint
 nsresult
 nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
 {
     NS_ENSURE_ARG_POINTER(outputStream);
     *outputStream = nullptr;
 
     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
         
-    NS_ASSERTION(!mOutStream, "already have an output stream open");
+    NS_ASSERTION(!mOutputStreamIsOpen, "already have an output stream open");
     NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
-    if (mOutStream || mInStreamCount)  return NS_ERROR_NOT_AVAILABLE;
+    if (mOutputStreamIsOpen || mInStreamCount)  return NS_ERROR_NOT_AVAILABLE;
     
     mStreamEnd = mBinding->mCacheEntry->DataSize();
 
     // Inits file or buffer and truncate at the desired offset
     nsresult rv = SeekAndTruncate(offset);
     if (NS_FAILED(rv)) return rv;
 
-    // create a new output stream
-    mOutStream = new nsDiskCacheOutputStream(this);
-    if (!mOutStream)  return NS_ERROR_OUT_OF_MEMORY;
-    
-    NS_ADDREF(*outputStream = mOutStream);
+    mOutputStreamIsOpen = true;
+    NS_ADDREF(*outputStream = this);
     return NS_OK;
 }
 
 nsresult
 nsDiskCacheStreamIO::ClearBinding()
 {
     nsresult rv = NS_OK;
-    if (mBinding && mOutStream)
-        rv = Flush();
+    if (mBinding && mOutputStreamIsOpen)
+        rv = CloseOutputStream();
     mBinding = nullptr;
     return rv;
 }
 
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Close()
+{
+    if (!mOutputStreamIsOpen) return NS_OK;
+
+    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+    // grab service lock
+    nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM));
+
+    if (!mBinding) {    // if we're severed, just clear member variables
+        mOutputStreamIsOpen = false;
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    nsresult rv = CloseOutputStream();
+    if (NS_FAILED(rv))
+        NS_WARNING("CloseOutputStream() failed");
+
+    mozilla::Telemetry::ID id;
+    if (NS_IsMainThread())
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE_MAIN_THREAD;
+    else
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE;
+    mozilla::Telemetry::AccumulateTimeDelta(id, start);
+
+    return rv;
+}
+
 nsresult
-nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream *  outputStream)
-{
-    nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
-
-    if (outputStream != mOutStream) {
-        NS_WARNING("mismatched output streams");
-        return NS_ERROR_UNEXPECTED;
-    }
-
-    // output stream is closing
-    if (!mBinding) {    // if we're severed, just clear member variables
-        mOutStream = nullptr;
-        outputStream->ReleaseStreamIO();
-        return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    nsresult rv = Flush();
-    if (NS_FAILED(rv))
-        NS_WARNING("Flush() failed");
-
-    mOutStream = nullptr;
-    return rv;
-}
-
-nsresult
-nsDiskCacheStreamIO::Flush()
+nsDiskCacheStreamIO::CloseOutputStream()
 {
     NS_ASSERTION(mBinding, "oops");
 
-    CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
+    CACHE_LOG_DEBUG(("CACHE: CloseOutputStream [%x doomed=%u]\n",
         mBinding->mRecord.HashNumber(), mBinding->mDoomed));
 
+    // Mark outputstream as closed, even if saving the stream fails
+    mOutputStreamIsOpen = false;
+
     // When writing to a file, just close the file
     if (mFD) {
         (void) PR_Close(mFD);
         mFD = nullptr;
         return NS_OK;
     }
 
     // write data to cache blocks, or flush mBuffer to file
@@ -507,23 +404,25 @@ nsDiskCacheStreamIO::Flush()
 }
 
 
 // assumptions:
 //      only one thread writing at a time
 //      never have both output and input streams open
 //      OnDataSizeChanged() will have already been called to update entry->DataSize()
 
-nsresult
+NS_IMETHODIMP
 nsDiskCacheStreamIO::Write( const char * buffer,
                             uint32_t     count,
                             uint32_t *   bytesWritten)
 {
     NS_ENSURE_ARG_POINTER(buffer);
     NS_ENSURE_ARG_POINTER(bytesWritten);
+    if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
+
     *bytesWritten = 0;  // always initialize to zero in case of errors
 
     NS_ASSERTION(count, "Write called with count of zero");
     if (count == 0) {
         return NS_OK;   // nothing to write
     }
 
     // grab service lock
@@ -761,8 +660,43 @@ nsDiskCacheStreamIO::SeekAndTruncate(uin
         nsresult rv = ReadCacheBlocks(kMaxBufferSize);
         if (NS_FAILED(rv))  return rv;
     }
 
     // stream buffer sanity check
     NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream");
     return NS_OK;
 }
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Flush()
+{
+    if (!mOutputStreamIsOpen)  return NS_BASE_STREAM_CLOSED;
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
+{
+    NS_NOTREACHED("WriteFrom");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader,
+                                        void *           closure,
+                                        uint32_t         count,
+                                        uint32_t *       bytesWritten)
+{
+    NS_NOTREACHED("WriteSegments");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
+{
+    *nonBlocking = false;
+    return NS_OK;
+}
--- a/netwerk/cache/nsDiskCacheStreams.h
+++ b/netwerk/cache/nsDiskCacheStreams.h
@@ -13,65 +13,57 @@
 #include "nsCache.h"
 
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 
 #include "pratom.h"
 
 class nsDiskCacheInputStream;
-class nsDiskCacheOutputStream;
 class nsDiskCacheDevice;
 
-class nsDiskCacheStreamIO : public nsISupports {
+class nsDiskCacheStreamIO : public nsIOutputStream {
 public:
              nsDiskCacheStreamIO(nsDiskCacheBinding *   binding);
     virtual ~nsDiskCacheStreamIO();
     
     NS_DECL_ISUPPORTS
+    NS_DECL_NSIOUTPUTSTREAM
 
     nsresult    GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
     nsresult    GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
 
-    nsresult    CloseOutputStream(nsDiskCacheOutputStream * outputStream);
-
-    nsresult    Write( const char * buffer,
-                       uint32_t     count,
-                       uint32_t *   bytesWritten);
-
     nsresult    ClearBinding();
     
     void        IncrementInputStreamCount() { PR_ATOMIC_INCREMENT(&mInStreamCount); }
     void        DecrementInputStreamCount()
                 {
                     PR_ATOMIC_DECREMENT(&mInStreamCount);
                     NS_ASSERTION(mInStreamCount >= 0, "mInStreamCount has gone negative");
                 }
 
     size_t     SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf);
 
     // GCC 2.95.2 requires this to be defined, although we never call it.
     // and OS/2 requires that it not be private
     nsDiskCacheStreamIO() { NS_NOTREACHED("oops"); }
 
 private:
-
-    void        Close();
     nsresult    OpenCacheFile(int flags, PRFileDesc ** fd);
     nsresult    ReadCacheBlocks(uint32_t bufferSize);
     nsresult    FlushBufferToFile();
     void        UpdateFileSize();
     void        DeleteBuffer();
-    nsresult    Flush();
+    nsresult    CloseOutputStream();
     nsresult    SeekAndTruncate(uint32_t offset);
 
     nsDiskCacheBinding *        mBinding;       // not an owning reference
     nsDiskCacheDevice *         mDevice;
-    nsDiskCacheOutputStream *   mOutStream;     // not an owning reference
     int32_t                     mInStreamCount;
     PRFileDesc *                mFD;
 
     uint32_t                    mStreamEnd;     // current size of data
     uint32_t                    mBufSize;       // current end of buffer
     char *                      mBuffer;
+    bool                        mOutputStreamIsOpen;
 };
 
 #endif // _nsDiskCacheStreams_h_
--- a/netwerk/dash/mpd/IMPDManager.h
+++ b/netwerk/dash/mpd/IMPDManager.h
@@ -81,17 +81,17 @@ public:
   // Gets the total number of |AdaptationSet|s in the first |Period|.
   // Usually, this should be 2 for audio and video.
   // XXX Future versions may require a |Period| index.
   // XXX Future versions may have multiple tracks for audio.
   virtual uint32_t GetNumAdaptationSets() const = 0;
 
   // Returns the media type for the given |AdaptationSet|, audio/video.
   virtual AdaptationSetType
-          GetAdaptationSetType(uint32_t aAdaptSetIdx) const = 0;
+          GetAdaptationSetType(uint32_t const aAdaptSetIdx) const = 0;
 
   // Gets the number of media |Representation|s for the given |AdaptationSet|.
   // e.g how many bitrate encodings are there of the audio stream?
   virtual uint32_t
           GetNumRepresentations(uint32_t const aAdaptSetIdx) const = 0;
 
   // Gets the specified |Representation| from the specified |AdaptationSet|,
   // e.g. get metadata about the 64Kbps encoding of the video stream.
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/LoadContext.h"
 #include "nsPrintfCString.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIAppsService.h"
 #include "nsEscape.h"
+#include "RemoteOpenFileParent.h"
 
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 using IPC::SerializedLoadContext;
 
 namespace mozilla {
 namespace net {
@@ -399,16 +400,24 @@ NeckoParent::AllocPRemoteOpenFile(const 
     }
   }
 
   RemoteOpenFileParent* parent = new RemoteOpenFileParent(fileURL);
   return parent;
 }
 
 bool
+NeckoParent::RecvPRemoteOpenFileConstructor(PRemoteOpenFileParent* aActor,
+                                            const URIParams& aFileURI,
+                                            PBrowserParent* aBrowser)
+{
+  return static_cast<RemoteOpenFileParent*>(aActor)->OpenSendCloseDelete();
+}
+
+bool
 NeckoParent::DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor)
 {
   delete actor;
   return true;
 }
 
 bool
 NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
@@ -423,9 +432,8 @@ NeckoParent::RecvCancelHTMLDNSPrefetch(c
                                  const uint16_t& flags,
                                  const nsresult& reason)
 {
   nsHTMLDNSPrefetch::CancelPrefetch(hostname, flags, reason);
   return true;
 }
 
 }} // mozilla::net
-
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -64,20 +64,26 @@ protected:
   virtual PWebSocketParent* AllocPWebSocket(PBrowserParent* browser,
                                             const SerializedLoadContext& aSerialized);
   virtual bool DeallocPWebSocket(PWebSocketParent*);
   virtual PTCPSocketParent* AllocPTCPSocket(const nsString& aHost,
                                             const uint16_t& aPort,
                                             const bool& useSSL,
                                             const nsString& aBinaryType,
                                             PBrowserParent* aBrowser);
-  virtual PRemoteOpenFileParent* AllocPRemoteOpenFile(
-                                            const URIParams& fileuri,
-                                            PBrowserParent* browser);
-  virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor);
+
+  virtual PRemoteOpenFileParent* AllocPRemoteOpenFile(const URIParams& aFileURI,
+                                                      PBrowserParent* aBrowser)
+                                                      MOZ_OVERRIDE;
+  virtual bool RecvPRemoteOpenFileConstructor(PRemoteOpenFileParent* aActor,
+                                              const URIParams& aFileURI,
+                                              PBrowserParent* aBrowser)
+                                              MOZ_OVERRIDE;
+  virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileParent* aActor)
+                                      MOZ_OVERRIDE;
 
   virtual bool RecvPTCPSocketConstructor(PTCPSocketParent*,
                                          const nsString& aHost,
                                          const uint16_t& aPort,
                                          const bool& useSSL,
                                          const nsString& aBinaryType,
                                          PBrowserParent* aBrowser);
   virtual bool DeallocPTCPSocket(PTCPSocketParent*);
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -42,16 +42,18 @@ parent:
   PCookieService();
   PHttpChannel(nullable PBrowser browser,
                SerializedLoadContext loadContext);
   PWyciwygChannel();
   PFTPChannel(PBrowser browser, SerializedLoadContext loadContext);
   PWebSocket(PBrowser browser, SerializedLoadContext loadContext);
   PTCPSocket(nsString host, uint16_t port, bool useSSL, nsString binaryType,
              nullable PBrowser browser);
+
+  // Request that the parent open a file.
   PRemoteOpenFile(URIParams fileuri, nullable PBrowser browser);
 
   HTMLDNSPrefetch(nsString hostname, uint16_t flags);
   CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
 
 };
 
 
--- a/netwerk/ipc/PRemoteOpenFile.ipdl
+++ b/netwerk/ipc/PRemoteOpenFile.ipdl
@@ -13,25 +13,16 @@ namespace net {
 /**
  * Protocol to support RemoteOpenFile, an nsIFile that opens it's file handle on
  * the parent instead of the child (since child lacks permission to do so).
  */
 protocol PRemoteOpenFile
 {
   manager PNecko;
 
-parent:
-  // Tell parent to open file. URI to open was passed and vetted for security in
-  // IPDL constructor: see NeckoParent::AllocPRemoteOpenFile()
-  AsyncOpenFile();
-
-  __delete__();
-
 child:
   // Your file handle is ready, Sir...
-  FileOpened(FileDescriptor fd);
-  // Trying to send invalid fd crashes, so we need separate method for failure
-  FileDidNotOpen();
+  __delete__(FileDescriptor fd);
 };
 
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/ipc/RemoteOpenFileChild.cpp
+++ b/netwerk/ipc/RemoteOpenFileChild.cpp
@@ -181,21 +181,17 @@ RemoteOpenFileChild::AsyncRemoteFileOpen
     }
   }
 
   URIParams uri;
   SerializeURI(mURI, uri);
 
   gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, mTabChild);
 
-  // Can't seem to reply from within IPDL Parent constructor, so send open as
-  // separate message
-  SendAsyncOpenFile();
-
-  // The chrome process now has a logical ref to us until we call Send__delete
+  // The chrome process now has a logical ref to us until it calls Send__delete.
   AddIPDLReference();
 
   mListener = aListener;
   mAsyncOpenCalled = true;
   return NS_OK;
 #endif
 }
 
@@ -211,23 +207,23 @@ RemoteOpenFileChild::OnCachedFileDescrip
     if (NS_FAILED(mFile->GetPath(path))) {
       MOZ_NOT_REACHED("Couldn't get path from file!");
     }
 
     MOZ_ASSERT(path == aPath, "Paths don't match!");
   }
 #endif
 
-  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ false);
+  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvDelete */ false);
 }
 
 void
 RemoteOpenFileChild::HandleFileDescriptorAndNotifyListener(
                                                       const FileDescriptor& aFD,
-                                                      bool aFromRecvFileOpened)
+                                                      bool aFromRecvDelete)
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
   MOZ_NOT_REACHED("OS X and Windows shouldn't be doing IPDL here");
 #else
   if (!mListener) {
     // We already notified our listener (either in response to a cached file
     // descriptor callback or through the normal messaging mechanism). Close the
     // file descriptor if it is valid.
@@ -238,19 +234,21 @@ RemoteOpenFileChild::HandleFileDescripto
     return;
   }
 
   MOZ_ASSERT(!mNSPRFileDesc);
 
   nsRefPtr<TabChild> tabChild;
   mTabChild.swap(tabChild);
 
-  // If there is a pending callback and we're being called from IPDL then we
-  // need to cancel it.
-  if (tabChild && aFromRecvFileOpened) {
+  // If RemoteOpenFile reply (Recv__delete__) for app's application.zip comes
+  // back sooner than the parent-pushed fd (TabChild::RecvCacheFileDescriptor())
+  // have TabChild cancel running callbacks, since we'll call them in
+  // NotifyListener.
+  if (tabChild && aFromRecvDelete) {
     nsString path;
     if (NS_FAILED(mFile->GetPath(path))) {
       MOZ_NOT_REACHED("Couldn't get path from file!");
     }
 
     tabChild->CancelCachedFileDescriptorCallback(path, this);
   }
 
@@ -280,43 +278,22 @@ RemoteOpenFileChild::NotifyListener(nsre
   }
 }
 
 //-----------------------------------------------------------------------------
 // RemoteOpenFileChild::PRemoteOpenFileChild
 //-----------------------------------------------------------------------------
 
 bool
-RemoteOpenFileChild::RecvFileOpened(const FileDescriptor& aFD)
+RemoteOpenFileChild::Recv__delete__(const FileDescriptor& aFD)
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
   NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here");
 #else
-  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ true);
-
-  // This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if
-  // IPDL holds the last reference.  Don't rely on |this| existing after here!
-  Send__delete__(this);
-#endif
-
-  return true;
-}
-
-bool
-RemoteOpenFileChild::RecvFileDidNotOpen()
-{
-#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
-  NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here");
-#else
-  HandleFileDescriptorAndNotifyListener(FileDescriptor(),
-                                        /* aFromRecvFileOpened */ true);
-
-  // This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if
-  // IPDL holds the last reference.  Don't rely on |this| existing after here!
-  Send__delete__(this);
+  HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvDelete */ true);
 #endif
 
   return true;
 }
 
 //-----------------------------------------------------------------------------
 // RemoteOpenFileChild::nsIFile functions that we override logic for
 //-----------------------------------------------------------------------------
--- a/netwerk/ipc/RemoteOpenFileChild.h
+++ b/netwerk/ipc/RemoteOpenFileChild.h
@@ -2,16 +2,17 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _RemoteOpenFileChild_h
 #define _RemoteOpenFileChild_h
 
+#include "mozilla/Attributes.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/net/PRemoteOpenFileChild.h"
 #include "nsICachedFileDescriptorListener.h"
 #include "nsILocalFile.h"
 #include "nsIRemoteOpenFileListener.h"
 
 namespace mozilla {
 
@@ -81,24 +82,23 @@ private:
   RemoteOpenFileChild(const RemoteOpenFileChild& other);
 
 protected:
   void AddIPDLReference()
   {
     AddRef();
   }
 
-  virtual bool RecvFileOpened(const FileDescriptor&);
-  virtual bool RecvFileDidNotOpen();
+  virtual bool Recv__delete__(const FileDescriptor&) MOZ_OVERRIDE;
 
   virtual void OnCachedFileDescriptor(const nsAString& aPath,
                                       const FileDescriptor& aFD) MOZ_OVERRIDE;
 
   void HandleFileDescriptorAndNotifyListener(const FileDescriptor&,
-                                             bool aFromRecvFileOpened);
+                                             bool aFromRecvDelete);
 
   void NotifyListener(nsresult aResult);
 
   // regular nsIFile object, that we forward most calls to.
   nsCOMPtr<nsIFile> mFile;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIRemoteOpenFileListener> mListener;
   nsRefPtr<TabChild> mTabChild;
--- a/netwerk/ipc/RemoteOpenFileParent.cpp
+++ b/netwerk/ipc/RemoteOpenFileParent.cpp
@@ -12,60 +12,51 @@
 #if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
 namespace mozilla {
 namespace net {
 
-RemoteOpenFileParent::RemoteOpenFileParent(nsIFileURL *aURI)
-  : mURI(aURI)
-#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
-  , mFd(-1)
-#endif
-{}
-
-RemoteOpenFileParent::~RemoteOpenFileParent()
-{
-#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
-  if (mFd != -1) {
-    // close file handle now that other process has it open, else we'll leak
-    // file handles in parent process
-    close(mFd);
-  }
-#endif
-}
-
 bool
-RemoteOpenFileParent::RecvAsyncOpenFile()
+RemoteOpenFileParent::OpenSendCloseDelete()
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
-  NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
+  MOZ_NOT_REACHED("OS X and Windows shouldn't be doing IPDL here");
 #else
 
   // TODO: make this async!
 
+  FileDescriptor fileDescriptor;
+
   nsAutoCString path;
   nsresult rv = mURI->GetFilePath(path);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "GetFilePath failed!");
+
   NS_UnescapeURL(path);
+
   if (NS_SUCCEEDED(rv)) {
     int fd = open(path.get(), O_RDONLY);
-    if (fd != -1) {
-      unused << SendFileOpened(FileDescriptor(fd));
-      // file handle needs to stay open until it's shared with child (and IPDL
-      // is async, so hasn't happened yet). Close in destructor.
-      mFd = fd;
-      return true;
+    if (fd == -1) {
+      printf_stderr("RemoteOpenFileParent: file '%s' was not found!\n",
+                    path.get());
+    } else {
+      fileDescriptor = FileDescriptor(fd);
     }
   }
 
-  // Note: sending an invalid file descriptor currently kills the child process:
-  // but that's ok for our use case (failing to open application.jar).
-  printf_stderr("RemoteOpenFileParent: file '%s' was not found!\n", path.get());
-  unused << SendFileDidNotOpen();
+  // Sending a potentially invalid file descriptor is just fine.
+  unused << Send__delete__(this, fileDescriptor);
+
+  if (fileDescriptor.IsValid()) {
+    // close file now that other process has it open, else we'll leak fds in the
+    // parent process.
+    close(fileDescriptor.PlatformHandle());
+  }
+
 #endif // OS_TYPE
 
   return true;
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/ipc/RemoteOpenFileParent.h
+++ b/netwerk/ipc/RemoteOpenFileParent.h
@@ -13,26 +13,22 @@
 #include "nsIFileURL.h"
 
 namespace mozilla {
 namespace net {
 
 class RemoteOpenFileParent : public PRemoteOpenFileParent
 {
 public:
-  RemoteOpenFileParent(nsIFileURL* aURI);
+  RemoteOpenFileParent(nsIFileURL* aURI)
+  : mURI(aURI)
+  {}
 
- ~RemoteOpenFileParent();
-
-  virtual bool RecvAsyncOpenFile();
+  bool OpenSendCloseDelete();
 
 private:
   nsCOMPtr<nsIFileURL> mURI;
-
-#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
-  int mFd;
-#endif
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_RemoteOpenFileParent_h
--- a/rdf/datasource/src/nsFileSystemDataSource.cpp
+++ b/rdf/datasource/src/nsFileSystemDataSource.cpp
@@ -208,23 +208,17 @@ FileSystemDataSource::Create(nsISupports
         return NS_ERROR_OUT_OF_MEMORY;
      
     nsresult rv = self->Init();
     NS_ENSURE_SUCCESS(rv, rv);
 
     return self->QueryInterface(aIID, aResult);
 }
 
-NS_IMPL_CYCLE_COLLECTION_0(FileSystemDataSource) 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDataSource)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDataSource)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDataSource)
-    NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
-    NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_ISUPPORTS1(FileSystemDataSource, nsIRDFDataSource)
 
 NS_IMETHODIMP
 FileSystemDataSource::GetURI(char **uri)
 {
     NS_PRECONDITION(uri != nullptr, "null ptr");
     if (! uri)
         return NS_ERROR_NULL_POINTER;
 
--- a/rdf/datasource/src/nsFileSystemDataSource.h
+++ b/rdf/datasource/src/nsFileSystemDataSource.h
@@ -8,28 +8,26 @@
 
 #include "nsIRDFDataSource.h"
 #include "nsIRDFLiteral.h"
 #include "nsIRDFResource.h"
 #include "nsIRDFService.h"
 #include "nsISupportsArray.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
-#include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 
 #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_WIN)
 #define USE_NC_EXTENSION
 #endif
 
 class FileSystemDataSource MOZ_FINAL : public nsIRDFDataSource
 {
 public:
-    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-    NS_DECL_CYCLE_COLLECTION_CLASS(FileSystemDataSource)
+    NS_DECL_ISUPPORTS
     NS_DECL_NSIRDFDATASOURCE
 
     static nsresult Create(nsISupports* aOuter,
                            const nsIID& aIID, void **aResult);
 
     ~FileSystemDataSource() { }
     nsresult Init();
 
--- a/toolkit/components/osfile/_PromiseWorker.jsm
+++ b/toolkit/components/osfile/_PromiseWorker.jsm
@@ -34,26 +34,26 @@ Queue.prototype = {
 };
 
 /**
  * An object responsible for dispatching messages to
  * a chrome worker and routing the responses.
  *
  * @param {string} url The url containing the source code for this worker,
  * as in constructor ChromeWorker.
- * @param {Function=} log Optionally, a logging function.
+ * @param {Function} log A logging function.
  *
  * @constructor
  */
 function PromiseWorker(url, log) {
   if (typeof url != "string") {
     throw new TypeError("Expecting a string");
   }
-  if (log != null && typeof log != "function") {
-    throw new TypeError("Expecting either null or a function");
+  if (typeof log !== "function") {
+    throw new TypeError("log is expected to be a function");
   }
   this._log = log;
   this._url = url;
 
   /**
    * The queue of deferred, waiting for the completion of their
    * respective job by the worker.
    *
@@ -93,19 +93,17 @@ PromiseWorker.prototype = {
      * of |OS.File.Error|. These are treated by |worker.onmessage|.
      * However, for other errors, we rely on DOM's mechanism for
      * serializing errors, which transmits these errors through
      * |worker.onerror|.
      *
      * @param {Error} error Some JS error.
      */
     worker.onerror = function onerror(error) {
-      if (self._log) {
-        self._log("Received uncaught error from worker", JSON.stringify(error.message), error.message);
-      }
+      self._log("Received uncaught error from worker", error.message);
       error.preventDefault();
       let {deferred} = self._queue.pop();
       deferred.reject(error);
     };
 
     /**
      * Receive messages from the worker, propagate them to the listeners.
      *
@@ -115,19 +113,17 @@ PromiseWorker.prototype = {
      *    some_error is an instance of |PromiseWorker.WorkerError|
      *
      * Messages may also contain a field |id| to help
      * with debugging.
      *
      * @param {*} msg The message received from the worker.
      */
     worker.onmessage = function onmessage(msg) {
-      if (self._log) {
-        self._log("Received message from worker", JSON.stringify(msg.data));
-      }
+      self._log("Received message from worker", msg.data);
       let handler = self._queue.pop();
       let deferred = handler.deferred;
       let data = msg.data;
       if (data.id != handler.id) {
         throw new Error("Internal error: expecting msg " + handler.id + ", " +
                         " got " + data.id + ": " + JSON.stringify(msg.data));
       }
       if ("ok" in data) {
@@ -153,24 +149,20 @@ PromiseWorker.prototype = {
    * garbage-collected before the message treatment is complete.
    *
    * @return {promise}
    */
   post: function post(fun, array, closure) {
     let deferred = Promise.defer();
     let id = ++this._id;
     let message = {fun: fun, args: array, id: id};
-    if (this._log) {
-      this._log("Posting message", JSON.stringify(message));
-    }
+    this._log("Posting message", message);
     this._queue.push({deferred:deferred, closure: closure, id: id});
     this._worker.postMessage(message);
-    if (this._log) {
-      this._log("Message posted");
-    }
+    this._log("Message posted");
     return deferred.promise;
   }
 };
 
 /**
  * An error that has been serialized by the worker.
  *
  * @constructor
--- a/toolkit/components/osfile/osfile_async_front.jsm
+++ b/toolkit/components/osfile/osfile_async_front.jsm
@@ -84,18 +84,17 @@ let clone = function clone(object) {
 
 /**
  * A shared constant used to normalize a set of options to nothing.
  */
 const noOptions = {};
 
 
 let worker = new PromiseWorker(
-  "resource://gre/modules/osfile/osfile_async_worker.js",
-  DEBUG?LOG:null);
+  "resource://gre/modules/osfile/osfile_async_worker.js", LOG);
 let Scheduler = {
   post: function post(...args) {
     let promise = worker.post.apply(worker, args);
     return promise.then(
       null,
       function onError(error) {
         // Decode any serialized error
         if (error instanceof PromiseWorker.WorkerError) {
--- a/toolkit/components/osfile/osfile_async_worker.js
+++ b/toolkit/components/osfile/osfile_async_worker.js
@@ -11,83 +11,65 @@ if (this.Components) {
 
 (function(exports) {
   "use strict";
 
    try {
      importScripts("resource://gre/modules/osfile.jsm");
 
      let LOG = exports.OS.Shared.LOG.bind(exports.OS.Shared.LOG, "Agent");
-     // A simple flag used to control debugging messages.
-     let DEBUG = exports.OS.Shared.DEBUG;
 
      /**
       * Communications with the controller.
       *
       * Accepts messages:
       * {fun:function_name, args:array_of_arguments_or_null, id:id}
       *
       * Sends messages:
       * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id}
       */
      self.onmessage = function onmessage(msg) {
        let data = msg.data;
-       if (DEBUG) {
-         LOG("Received message", JSON.stringify(data));
-       }
+       LOG("Received message", data);
        let id = data.id;
        let result;
        let exn;
        try {
          let method = data.fun;
-         if (DEBUG) {
-           LOG("Calling method", method);
-         }
+         LOG("Calling method", method);
          result = Agent[method].apply(Agent, data.args);
-         if (DEBUG) {
-           LOG("Method", method, "succeeded");
-         }
+         LOG("Method", method, "succeeded");
        } catch (ex) {
          exn = ex;
-         if (DEBUG) {
-           LOG("Error while calling agent method", exn, exn.stack);
-         }
+         LOG("Error while calling agent method", exn, exn.stack);
        }
        // Now, post a reply, possibly as an uncaught error.
        // We post this message from outside the |try ... catch| block
        // to avoid capturing errors that take place during |postMessage| and
        // built-in serialization.
        if (!exn) {
-         if (DEBUG) {
-           LOG("Sending positive reply", JSON.stringify(result), "id is", id);
-         }
+         LOG("Sending positive reply", result, "id is", id);
          if (result instanceof Transfer) {
            // Take advantage of zero-copy transfers
            self.postMessage({ok: result.data, id: id}, result.transfers);
          } else {
            self.postMessage({ok: result, id:id});
          }
        } else if (exn == StopIteration) {
          // StopIteration cannot be serialized automatically
-         if (DEBUG) {
-           LOG("Sending back StopIteration");
-         }
+         LOG("Sending back StopIteration");
          self.postMessage({StopIteration: true, id: id});
        } else if (exn instanceof exports.OS.File.Error) {
-         if (DEBUG) {
-           LOG("Sending back OS.File error", exn, "id is", id);
-         }
+         LOG("Sending back OS.File error", exn, "id is", id);
          // Instances of OS.File.Error know how to serialize themselves
          // (deserialization ensures that we end up with OS-specific
          // instances of |OS.File.Error|)
          self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id});
        } else {
-         if (DEBUG) {
-           LOG("Sending back regular error", exn, exn.stack, "id is", id);
-         }
+         LOG("Sending back regular error", exn, exn.stack, "id is", id);
          // Other exceptions do not, and should be propagated through DOM's
          // built-in mechanism for uncaught errors, although this mechanism
          // may lose interesting information.
          throw exn;
        }
      };
 
      /**
@@ -197,24 +179,24 @@ if (this.Components) {
      /**
       * The agent.
       *
       * It is in charge of performing method-specific deserialization
       * of messages, calling the function/method of OS.File and serializing
       * back the results.
       */
      let Agent = {
-       // Update DEBUG flag message from controller.
+       // Update worker's OS.Shared.DEBUG flag message from controller.
        SET_DEBUG: function SET_DEBUG (aDEBUG) {
-         DEBUG = aDEBUG;
+         exports.OS.Shared.DEBUG = aDEBUG;
        },
-       // Return current DEBUG value to controller.
+       // Return worker's current OS.Shared.DEBUG value to controller.
        // Note: This is used for testing purposes.
        GET_DEBUG: function GET_DEBUG () {
-         return DEBUG;
+         return exports.OS.Shared.DEBUG;
        },
        // Functions of OS.File
        stat: function stat(path) {
          return exports.OS.File.Info.toMsg(
            exports.OS.File.stat(Type.path.fromMsg(path)));
        },
        getCurrentDirectory: function getCurrentDirectory() {
          return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
--- a/toolkit/components/osfile/osfile_shared_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_shared_allthreads.jsm
@@ -96,17 +96,58 @@
        LOG = function() {
          let text = "OS";
          for (let i = 0; i < arguments.length; ++i) {
            text += (" " + arguments[i]);
          }
          dump(text + "\n");
        };
      }
-     exports.OS.Shared.LOG = LOG;
+
+     /**
+      * Apply JSON.stringify to an argument of type object.
+      * Return itself otherwise.
+      *
+      * @param arg An argument to be stringified if possible.
+      */
+     let stringifyArg = function stringifyArg(arg) {
+       if (typeof arg === "string") {
+         return arg;
+       }
+       if (arg && typeof arg === "object") {
+         return JSON.stringify(arg);
+       }
+       return arg;
+     };
+
+     /**
+      * A Shared LOG utility function that only logs when DEBUG is set.
+      *
+      * @params {string|object}
+      *         An arbitrary number of arguments to be logged.
+      */
+     exports.OS.Shared.LOG = function (...args) {
+       // If DEBUG is falsy, do nothing.
+       if (!exports.OS.Shared.DEBUG) {
+         return;
+       }
+
+       let logFunc = LOG;
+
+       if (exports.OS.Shared.TEST && Services) {
+         // If TEST is true and the file is loaded in the main thread,
+         // use Services.console for logging and listening to.
+         logFunc = function logFunc(...args) {
+           let message = ["TEST", "OS"].concat(args).join(" ");
+           Services.console.logStringMessage(message + "\n");
+         };
+       }
+
+       logFunc.apply(null, [stringifyArg(arg) for (arg of args)]);
+     };
 
      /**
       * An OS error.
       *
       * This class is provided mostly for type-matching. If you need more
       * details about an error, you should use the platform-specific error
       * codes provided by subclasses of |OS.Shared.Error|.
       *
@@ -378,51 +419,43 @@
        }
        if (!("value" in x)) { // Sanity check
          throw new TypeError("Number " + x.toSource() + " has no field |value|");
        }
        return x.value;
      };
 
      function projector(type, signed) {
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Determining best projection for", type,
-             "(size: ", type.size, ")", signed?"signed":"unsigned");
-       }
+       exports.OS.Shared.LOG("Determining best projection for", type,
+         "(size: ", type.size, ")", signed?"signed":"unsigned");
        if (type instanceof Type) {
          type = type.implementation;
        }
        if (!type.size) {
          throw new TypeError("Argument is not a proper C type");
        }
        // Determine if type is projected to Int64/Uint64
        if (type.size == 8           // Usual case
            // The following cases have special treatment in js-ctypes
            // Regardless of their size, the value getter returns
            // a Int64/Uint64
            || type == ctypes.size_t // Special cases
            || type == ctypes.ssize_t
            || type == ctypes.intptr_t
            || type == ctypes.uintptr_t
-           || type == ctypes.off_t){
-          if (signed) {
-	    if (exports.OS.Shared.DEBUG) {
-             LOG("Projected as a large signed integer");
-	    }
-            return projectLargeInt;
-          } else {
-	    if (exports.OS.Shared.DEBUG) {
-             LOG("Projected as a large unsigned integer");
-	    }
-            return projectLargeUInt;
-          }
+           || type == ctypes.off_t) {
+         if (signed) {
+           exports.OS.Shared.LOG("Projected as a large signed integer");
+           return projectLargeInt;
+         } else {
+           exports.OS.Shared.LOG("Projected as a large unsigned integer");
+           return projectLargeUInt;
+         }
        }
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Projected as a regular number");
-       }
+       exports.OS.Shared.LOG("Projected as a regular number");
        return projectValue;
      };
      exports.OS.Shared.projectValue = projectValue;
 
 
 
      /**
       * Get the appropriate type for an unsigned int of the given size.
@@ -815,19 +848,17 @@
       * @return null if the function could not be defined (generally because
       * it does not exist), or a JavaScript wrapper performing the call to C
       * and any type conversion required.
       */// Note: Future versions will use a different implementation of this
         // function on the main thread, osfile worker thread and regular worker
         // thread
      let declareFFI = function declareFFI(lib, symbol, abi,
                                           returnType /*, argTypes ...*/) {
-       if (exports.OS.Shared.DEBUG) {
-         LOG("Attempting to declare FFI ", symbol);
-       }
+       exports.OS.Shared.LOG("Attempting to declare FFI ", symbol);
        // We guard agressively, to avoid any late surprise
        if (typeof symbol != "string") {
          throw new TypeError("declareFFI expects as first argument a string");
        }
        abi = abi || ctypes.default_abi;
        if (Object.prototype.toString.call(abi) != "[object CABI]") {
          // Note: This is the only known manner of checking whether an object
          // is an abi.
@@ -855,26 +886,22 @@
          let fun = lib.declare.apply(lib, signature);
          let result = function ffi(/*arguments*/) {
            let result = fun.apply(fun, arguments);
            return returnType.importFromC(result, symbol);
          };
          if (exports.OS.Shared.DEBUG) {
            result.fun = fun; // Also return the raw FFI function.
          }
-	 if (exports.OS.Shared.DEBUG) {
-          LOG("Function", symbol, "declared");
-	 }
+         exports.OS.Shared.LOG("Function", symbol, "declared");
          return result;
        } catch (x) {
          // Note: Not being able to declare a function is normal.
          // Some functions are OS (or OS version)-specific.
-	 if (exports.OS.Shared.DEBUG) {
-          LOG("Could not declare function " + symbol, x);
-	 }
+         exports.OS.Shared.LOG("Could not declare function ", symbol, x);
          return null;
        }
      };
      exports.OS.Shared.declareFFI = declareFFI;
 
      // A bogus array type used to perform pointer arithmetics
      let gOffsetByType;
 
--- a/toolkit/components/osfile/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/osfile_unix_allthreads.jsm
@@ -42,19 +42,17 @@ if (typeof Components != "undefined") {
   let libc_candidates =  [ "libSystem.B.dylib",
                            "libc.so.6",
                            "libc.so" ];
   for (let i = 0; i < libc_candidates.length; ++i) {
     try {
       libc = ctypes.open(libc_candidates[i]);
       break;
     } catch (x) {
-      if (exports.OS.Shared.DEBUG) {
-        LOG("Could not open libc "+libc_candidates[i]);
-      }
+      LOG("Could not open libc ", libc_candidates[i]);
     }
   }
   if (!libc) {
     throw new Error("Could not open any libc.");
   }
   exports.OS.Shared.Unix.libc = libc;
 
   // Define declareFFI
--- a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
+++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js
@@ -135,16 +135,17 @@ let test = maketest("Main", function mai
     yield test_info_features_detect();
     yield test_read_write();
     yield test_read_write_all();
     yield test_position();
     yield test_copy();
     yield test_mkdir();
     yield test_iter();
     yield test_exists();
+    yield test_debug_test();
     info("Test is over");
     SimpleTest.finish();
   });
 });
 
 /**
  * A file that we know exists and that can be used for reading.
  */
@@ -648,9 +649,46 @@ let test_debug = maketest("debug", funct
     }
     testSetDebugPref(true);
     let workerDEBUG = yield OS.File.GET_DEBUG();
     test.is(workerDEBUG, true, "Worker's DEBUG is set.");
     testSetDebugPref(false);
     workerDEBUG = yield OS.File.GET_DEBUG();
     test.is(workerDEBUG, false, "Worker's DEBUG is unset.");
   });
+});
+
+/**
+ * Test logging in the main thread with set OS.Shared.DEBUG and
+ * OS.Shared.TEST flags.
+ */
+let test_debug_test = maketest("debug_test", function debug_test(test) {
+  return Task.spawn(function () {
+    // Create a console listener.
+    let consoleListener = {
+      observe: function (aMessage) {
+        // Ignore unexpected messages.
+        if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) {
+          return;
+        }
+        if (aMessage.message.indexOf("TEST OS") < 0) {
+          return;
+        }
+        test.ok(true, "DEBUG TEST messages are logged correctly.")
+      }
+    };
+    // Set/Unset OS.Shared.DEBUG, OS.Shared.TEST and the console listener.
+    function toggleDebugTest (pref) {
+      OS.Shared.DEBUG = pref;
+      OS.Shared.TEST = pref;
+      Services.console[pref ? "registerListener" : "unregisterListener"](
+        consoleListener);
+    }
+    // Save original DEBUG value.
+    let originalPref = OS.Shared.DEBUG;
+    toggleDebugTest(true);
+    // Execution of OS.File.exist method will trigger OS.File.LOG several times.
+    let fileExists = yield OS.File.exists(EXISTING_FILE);
+    toggleDebugTest(false);
+    // Restore DEBUG to its original.
+    OS.Shared.DEBUG = originalPref;
+  });
 });
\ No newline at end of file
--- a/toolkit/components/passwordmgr/test/test_basic_form_html5.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_html5.html
@@ -148,17 +148,17 @@ function startTest() {
   is($_(8, "uname").value, "", "type=month should not be considered a username");
   
   is($_(9, "uname").value, "", "type=week should not be considered a username");
   
   is($_(10, "uname").value, "", "type=time should not be considered a username");
   
   is($_(11, "uname").value, "", "type=datetime-local should not be considered a username");
   
-  is($_(12, "uname").value, "", "type=range should not be considered a username");
+  is($_(12, "uname").value, "50", "type=range should not be considered a username");
   
   is($_(13, "uname").value, "", "type=color should not be considered a username");
 
   pwmgr.removeLogin(login1);
   pwmgr.removeLogin(login2);
   pwmgr.removeLogin(login3);
   pwmgr.removeLogin(login4);
  
--- a/toolkit/components/printing/content/printPreviewBindings.xml
+++ b/toolkit/components/printing/content/printPreviewBindings.xml
@@ -289,17 +289,19 @@
             return;
           }
 
           if (aValue == "Custom") {
             aValue = this.promptForScaleValue(settings.scaling * 100.0);
             if (aValue >= 10) {
               aValue /= 100.0;
             } else {
-              this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute('lastValidInx');
+              if (this.mScaleCombobox.hasAttribute('lastValidInx')) {
+                this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute('lastValidInx');
+              }
               return;
             }
           }
 
           this.setScaleCombobox(aValue);
           this.mScaleCombobox.setAttribute('lastValidInx', this.mScaleCombobox.selectedIndex);
 
           if (settings.scaling != aValue || settings.shrinkToFit)
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -99,16 +99,21 @@ Form History test: form field autocomple
   </form>
 
   <!-- form with input type='time' -->
   <form id="form15" onsubmit="return false;">
     <input  type="time" name="field12">
     <button type="submit">Submit</button>
   </form>
 
+  <!-- form with input type='range' -->
+  <form id="form16" onsubmit="return false;">
+    <input  type="range" name="field13" max="64">
+    <button type="submit">Submit</button>
+  </form>
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Form History autocomplete **/
 
@@ -144,20 +149,21 @@ fh.addEntry("field5", "123");
 fh.addEntry("field5", "1234");
 fh.addEntry("field6", "value");
 fh.addEntry("field7", "value");
 fh.addEntry("field8", "value");
 fh.addEntry("field9", "value");
 fh.addEntry("field10", "42");
 fh.addEntry("field11", "2010-10-10");
 fh.addEntry("field12", "21:21");
+fh.addEntry("field13", "32"); // not used, since type=range doesn't have a drop down menu
 fh.addEntry("searchbar-history", "blacklist test");
 
 // All these non-implemeted types might need autocomplete tests in the future.
-var todoTypes = [ "datetime", "month", "week", "datetime-local", "range", "color" ];
+var todoTypes = [ "datetime", "month", "week", "datetime-local", "color" ];
 var todoInput = document.createElement("input");
 for (var type of todoTypes) {
   todoInput.type = type;
   todo_is(todoInput.type, type, type + " type shouldn't be implemented");
 }
 
 
 function setForm(value) {
@@ -761,16 +767,27 @@ function runTest(testNum) {
         break;
 
     case 406:
         checkMenuEntries(["21:21"]);
         doKey("down");
         doKey("return");
         checkForm("21:21");
 
+        input = $_(16, "field13");
+        restoreForm();
+        doKey("down");
+        break;
+
+      case 407:
+        checkMenuEntries([]); // type=range does not have a drop down menu
+        doKey("down");
+        doKey("return");
+        checkForm("32"); // default (midway between minimum (0) and maximum (64))
+
         // Go to test 500.
         fh.addEntry("field1", "value1");
         input = $_(1, "field1");
         testNum = 499;
 
         restoreForm();
         doKey("down");
         break;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1892,29 +1892,29 @@
   },
   "NETWORK_DISK_CACHE_REVALIDATION": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Total Time spent (ms) during disk cache revalidation"
   },
-  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE": {
+  "NETWORK_DISK_CACHE_STREAMIO_CLOSE": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
-    "description": "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)"
-  },
-  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD": {
+    "description": "Time spent in nsDiskCacheStreamIO::Close() on non-main thread (ms)"
+  },
+  "NETWORK_DISK_CACHE_STREAMIO_CLOSE_MAIN_THREAD": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
-    "description": "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)"
+    "description": "Time spent in nsDiskCacheStreamIO::Close() on the main thread (ms)"
   },
   "IDLE_NOTIFY_BACK_MS": {
     "kind": "exponential",
     "high": "5000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Time spent checking for and notifying listeners that the user is back (ms)"
   },
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -789,20 +789,18 @@ struct Skippable
   NS_METHOD                                                                    \
   NS_CYCLE_COLLECTION_CLASSNAME(_class)::UnrootImpl(void *p)                   \
   {                                                                            \
     _class *tmp = static_cast<_class*>(p);                                     \
     tmp->_unroot_function();                                                   \
     return NS_OK;                                                              \
   }
 
-#define NS_IMPL_CYCLE_COLLECTION_0(_class)                                     \
- NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class)                                     \
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class)                               \
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+// NS_IMPL_CYCLE_COLLECTION_0 is not defined because most of the time it doesn't
+// make sense to add something to the CC that doesn't traverse to anything.
 
 #define NS_IMPL_CYCLE_COLLECTION_1(_class, _f)                                 \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class)                                 \
  NS_IMPL_CYCLE_COLLECTION_UNLINK(_f)                                           \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_END                                           \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class)                               \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f)                                         \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -938,22 +936,16 @@ struct Skippable
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f4)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f5)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f6)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f7)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f8)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f9)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-#define NS_IMPL_CYCLE_COLLECTION_INHERITED_0(_class, _base)                    \
- NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base)                \
- NS_IMPL_CYCLE_COLLECTION_UNLINK_END                                           \
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base)              \
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
 #define NS_IMPL_CYCLE_COLLECTION_INHERITED_1(_class, _base, _f1)               \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base)                \
  NS_IMPL_CYCLE_COLLECTION_UNLINK(_f1)                                          \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_END                                           \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base)              \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_f1)                                        \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END