Merge mozilla-beta to b2g37. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 05 Mar 2015 19:00:15 -0500
changeset 237461 e9d29a3b94e5524786127687d6e76f598a4b4b75
parent 237444 095de5dd0e8490a15d8e24b28c8e94a45b64e491 (current diff)
parent 237460 e4f2ee87f064843e6bd257dcc2427f267fb16220 (diff)
child 237462 a04034e239fb6dd6fe225531db8cdcc184a8d3ac
push id275
push userryanvm@gmail.com
push dateFri, 06 Mar 2015 00:00:13 +0000
treeherdermozilla-b2g37_v2_2@e9d29a3b94e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0
Merge mozilla-beta to b2g37. a=merge
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -923,16 +923,18 @@ const CustomizableWidgets = [
       win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
     }
   }, {
     id: "loop-button",
     type: "custom",
     label: "loop-call-button3.label",
     tooltiptext: "loop-call-button3.tooltiptext",
     defaultArea: CustomizableUI.AREA_NAVBAR,
+    // Not in private browsing, see bug 1108187.
+    showInPrivateBrowsing: false,
     introducedInVersion: 4,
     onBuild: function(aDocument) {
       // If we're not supposed to see the button, return zip.
       if (!Services.prefs.getBoolPref("loop.enabled")) {
         return null;
       }
 
       let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -83,17 +83,17 @@ nsPlainTextSerializer::nsPlainTextSerial
 
   // Line breaker
   mWrapColumn = 72;     // XXX magic number, we expect someone to reset this
   mCurrentLineWidth = 0;
 
   // Flow
   mEmptyLines = 1; // The start of the document is an "empty line" in itself,
   mInWhitespace = false;
-  mPreFormatted = false;
+  mPreFormattedMail = false;
   mStartedOutput = false;
 
   mPreformattedBlockBoundary = false;
 
   // initialize the tag stack to zero:
   // The stack only ever contains pointers to static atoms, so they don't
   // need refcounting.
   mTagStack = new nsIAtom*[TagStackSize];
@@ -494,33 +494,33 @@ nsPlainTextSerializer::DoOpenContainer(n
     // Ignore everything that follows the current tag in 
     // question until a matching end tag is encountered.
     mIgnoreAboveIndex = mTagStackIndex - 1;
     return NS_OK;
   }
 
   if (aTag == nsGkAtoms::body) {
     // Try to figure out here whether we have a
-    // preformatted style attribute.
+    // preformatted style attribute set by Thunderbird.
     //
     // Trigger on the presence of a "pre-wrap" in the
     // style attribute. That's a very simplistic way to do
     // it, but better than nothing.
     // Also set mWrapColumn to the value given there
     // (which arguably we should only do if told to do so).
     nsAutoString style;
     int32_t whitespace;
     if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::style, style)) &&
        (kNotFound != (whitespace = style.Find("white-space:")))) {
 
       if (kNotFound != style.Find("pre-wrap", true, whitespace)) {
 #ifdef DEBUG_preformatted
-        printf("Set mPreFormatted based on style pre-wrap\n");
+        printf("Set mPreFormattedMail based on style pre-wrap\n");
 #endif
-        mPreFormatted = true;
+        mPreFormattedMail = true;
         int32_t widthOffset = style.Find("width:");
         if (widthOffset >= 0) {
           // We have to search for the ch before the semicolon,
           // not for the semicolon itself, because nsString::ToInteger()
           // considers 'c' to be a valid numeric char (even if radix=10)
           // but then gets confused if it sees it next to the number
           // when the radix specified was 10, and returns an error code.
           int32_t semiOffset = style.Find("ch", false, widthOffset+6);
@@ -536,26 +536,26 @@ nsPlainTextSerializer::DoOpenContainer(n
 #ifdef DEBUG_preformatted
             printf("Set wrap column to %d based on style\n", mWrapColumn);
 #endif
           }
         }
       }
       else if (kNotFound != style.Find("pre", true, whitespace)) {
 #ifdef DEBUG_preformatted
-        printf("Set mPreFormatted based on style pre\n");
+        printf("Set mPreFormattedMail based on style pre\n");
 #endif
-        mPreFormatted = true;
+        mPreFormattedMail = true;
         mWrapColumn = 0;
       }
     } 
     else {
       /* See comment at end of function. */
       mInWhitespace = true;
-      mPreFormatted = false;
+      mPreFormattedMail = false;
     }
 
     return NS_OK;
   }
 
   // Keep this in sync with DoCloseContainer!
   if (!DoOutput()) {
     return NS_OK;
@@ -1030,17 +1030,17 @@ nsPlainTextSerializer::DoAddText(bool aI
   if (aIsLineBreak) {
     // The only times we want to pass along whitespace from the original
     // html source are if we're forced into preformatted mode via flags,
     // or if we're prettyprinting and we're inside a <pre>.
     // Otherwise, either we're collapsing to minimal text, or we're
     // prettyprinting to mimic the html format, and in neither case
     // does the formatting of the html source help us.
     if ((mFlags & nsIDocumentEncoder::OutputPreformatted) ||
-        (mPreFormatted && !mWrapColumn) ||
+        (mPreFormattedMail && !mWrapColumn) ||
         IsInPre()) {
       EnsureVerticalSpace(mEmptyLines+1);
     }
     else if (!mInWhitespace) {
       Write(kSpace);
       mInWhitespace = true;
     }
     return;
@@ -1551,24 +1551,24 @@ nsPlainTextSerializer::Write(const nsASt
       else
         break;
     }
   }
 
   // We have two major codepaths here. One that does preformatted text and one
   // that does normal formatted text. The one for preformatted text calls
   // Output directly while the other code path goes through AddToLine.
-  if ((mPreFormatted && !mWrapColumn) || IsInPre()
+  if ((mPreFormattedMail && !mWrapColumn) || (IsInPre() && !mPreFormattedMail)
       || ((mSpanLevel > 0 || mDontWrapAnyQuotes)
           && mEmptyLines >= 0 && str.First() == char16_t('>'))) {
     // No intelligent wrapping.
 
     // This mustn't be mixed with intelligent wrapping without clearing
     // the mCurrentLine buffer before!!!
-    NS_ASSERTION(mCurrentLine.IsEmpty() || IsInPre(),
+    NS_ASSERTION(mCurrentLine.IsEmpty() || (IsInPre() && !mPreFormattedMail),
                  "Mixed wrapping data and nonwrapping data on the same line");
     if (!mCurrentLine.IsEmpty()) {
       FlushLine();
     }
 
     // Put the mail quote "> " chars in, if appropriate.
     // Have to put it in before every line.
     while(bol<totLen) {
@@ -1696,17 +1696,17 @@ nsPlainTextSerializer::Write(const nsASt
         if (offsetIntoBuffer[0] == '\n' && IS_CJ_CHAR(offsetIntoBuffer[-1]) && IS_CJ_CHAR(offsetIntoBuffer[1])) {
           offsetIntoBuffer = str.get() + bol;
           AddToLine(offsetIntoBuffer, nextpos-bol);
           bol = nextpos + 1;
           continue;
         }
       }
       // If we're already in whitespace and not preformatted, just skip it:
-      if (mInWhitespace && (nextpos == bol) && !mPreFormatted &&
+      if (mInWhitespace && (nextpos == bol) && !mPreFormattedMail &&
           !(mFlags & nsIDocumentEncoder::OutputPreformatted)) {
         // Skip whitespace
         bol++;
         continue;
       }
 
       if (nextpos == bol) {
         // Note that we are in whitespace.
@@ -1715,17 +1715,17 @@ nsPlainTextSerializer::Write(const nsASt
         AddToLine(offsetIntoBuffer, 1);
         bol++;
         continue;
       }
       
       mInWhitespace = true;
       
       offsetIntoBuffer = str.get() + bol;
-      if (mPreFormatted || (mFlags & nsIDocumentEncoder::OutputPreformatted)) {
+      if (mPreFormattedMail || (mFlags & nsIDocumentEncoder::OutputPreformatted)) {
         // Preserve the real whitespace character
         nextpos++;
         AddToLine(offsetIntoBuffer, nextpos-bol);
         bol = nextpos;
       } 
       else {
         // Replace the whitespace with a space
         AddToLine(offsetIntoBuffer, nextpos-bol);
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -160,17 +160,18 @@ protected:
   int32_t          mSpanLevel;
 
 
   int32_t          mEmptyLines; // Will be the number of empty lines before
                                 // the current. 0 if we are starting a new
                                 // line and -1 if we are in a line.
 
   bool             mInWhitespace;
-  bool             mPreFormatted;
+  bool             mPreFormattedMail; // we're dealing with special DOM
+                                      // used by Thunderbird code.
   bool             mStartedOutput; // we've produced at least a character
 
   // While handling a new tag, this variable should remind if any line break
   // is due because of a closing tag. Setting it to "TRUE" while closing the tags.
   // Hence opening tags are guaranteed to start with appropriate line breaks.
   bool             mLineBreakDue;
 
   bool             mPreformattedBlockBoundary;
--- a/dom/base/test/TestPlainTextSerializer.cpp
+++ b/dom/base/test/TestPlainTextSerializer.cpp
@@ -158,16 +158,49 @@ TestBlockElement()
     return NS_ERROR_FAILURE;
   }
 
   passed("prettyprinted HTML to text serialization test");
   return NS_OK;
 }
 
 nsresult
+TestPreWrapElementForThunderbird()
+{
+  // This test examines the magic pre-wrap setup that Thunderbird relies on.
+  nsString test;
+  test.AppendLiteral(
+    "<html>" NS_LINEBREAK
+    "<body style=\"white-space: pre-wrap; width: 10ch;\">" NS_LINEBREAK
+    "<pre>" NS_LINEBREAK
+    "  first line is too long" NS_LINEBREAK
+    "  second line is even loooonger  " NS_LINEBREAK
+    "</pre>" NS_LINEBREAK
+    "</body>" NS_LINEBREAK "</html>");
+
+  ConvertBufToPlainText(test, nsIDocumentEncoder::OutputWrap);
+  // "\n\n  first\nline is\ntoo long\n  second\nline is\neven\nloooonger\n\n\n"
+  if (!test.EqualsLiteral(NS_LINEBREAK NS_LINEBREAK
+                          "  first" NS_LINEBREAK
+                          "line is" NS_LINEBREAK
+                          "too long" NS_LINEBREAK
+                          "  second" NS_LINEBREAK
+                          "line is" NS_LINEBREAK
+                          "even" NS_LINEBREAK
+                          "loooonger" NS_LINEBREAK
+                          NS_LINEBREAK NS_LINEBREAK)) {
+    fail("Wrong prettyprinted html to text serialization");
+    return NS_ERROR_FAILURE;
+  }
+
+  passed("prettyprinted HTML to text serialization test");
+  return NS_OK;
+}
+
+nsresult
 TestPlainTextSerializer()
 {
   nsString test;
   test.AppendLiteral("<html><base>base</base><head><span>span</span></head>"
                      "<body>body</body></html>");
   ConvertBufToPlainText(test, 0);
   if (!test.EqualsLiteral("basespanbody")) {
     fail("Wrong html to text serialization");
@@ -186,16 +219,19 @@ TestPlainTextSerializer()
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = TestPreElement();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = TestBlockElement();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = TestPreWrapElementForThunderbird();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Add new tests here...
   return NS_OK;
 }
 
 int main(int argc, char** argv)
 {
   ScopedXPCOM xpcom("PlainTextSerializer");
   if (xpcom.failed())
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -7163,17 +7163,17 @@ HTMLInputElement::GetCols()
   }
 
   return DEFAULT_COLS;
 }
 
 NS_IMETHODIMP_(int32_t)
 HTMLInputElement::GetWrapCols()
 {
-  return -1; // only textarea's can have wrap cols
+  return 0; // only textarea's can have wrap cols
 }
 
 NS_IMETHODIMP_(int32_t)
 HTMLInputElement::GetRows()
 {
   return DEFAULT_ROWS;
 }
 
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -1467,17 +1467,17 @@ HTMLTextAreaElement::GetCols()
 NS_IMETHODIMP_(int32_t)
 HTMLTextAreaElement::GetWrapCols()
 {
   // wrap=off means -1 for wrap width no matter what cols is
   nsHTMLTextWrap wrapProp;
   nsITextControlElement::GetWrapPropertyEnum(this, wrapProp);
   if (wrapProp == nsITextControlElement::eHTMLTextWrap_Off) {
     // do not wrap when wrap=off
-    return -1;
+    return 0;
   }
 
   // Otherwise we just wrap at the given number of columns
   return GetCols();
 }
 
 
 NS_IMETHODIMP_(int32_t)
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1707,17 +1707,17 @@ nsTextEditorState::InitializeRootNode()
   mRootNode->SetFlags(NODE_IS_EDITABLE);
 
   // Set the necessary classes on the text control. We use class values
   // instead of a 'style' attribute so that the style comes from a user-agent
   // style sheet and is still applied even if author styles are disabled.
   nsAutoString classValue;
   classValue.AppendLiteral("anonymous-div");
   int32_t wrapCols = GetWrapCols();
-  if (wrapCols >= 0) {
+  if (wrapCols > 0) {
     classValue.AppendLiteral(" wrap");
   }
   if (!IsSingleLineTextControl()) {
     // We can't just inherit the overflow because setting visible overflow will
     // crash when the number of lines exceeds the height of the textarea and
     // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP)
     // doesn't paint the caret for some reason.
     const nsStyleDisplay* disp = mBoundFrame->StyleDisplay();
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -122,16 +122,17 @@ MediaSourceReader::RequestAudioData()
     mAudioPromise.Reject(CANCELED, __func__);
     return p;
   }
   MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
 
   SwitchSourceResult ret = SwitchAudioSource(&mLastAudioTime);
   switch (ret) {
     case SOURCE_NEW:
+      GetAudioReader()->ResetDecode();
       mAudioSeekRequest.Begin(GetAudioReader()->Seek(GetReaderAudioTime(mLastAudioTime), 0)
                               ->RefableThen(GetTaskQueue(), __func__, this,
                                             &MediaSourceReader::CompleteAudioSeekAndDoRequest,
                                             &MediaSourceReader::CompleteAudioSeekAndRejectPromise));
       break;
     case SOURCE_NONE:
       if (mLastAudioTime) {
         CheckForWaitOrEndOfStream(MediaData::AUDIO_DATA, mLastAudioTime);
@@ -235,16 +236,17 @@ MediaSourceReader::OnAudioNotDecoded(Not
   // switching to the end of the buffered range.
   MOZ_ASSERT(aReason == END_OF_STREAM);
   if (mAudioSourceDecoder) {
     AdjustEndTime(&mLastAudioTime, mAudioSourceDecoder);
   }
 
   // See if we can find a different source that can pick up where we left off.
   if (SwitchAudioSource(&mLastAudioTime) == SOURCE_NEW) {
+    GetAudioReader()->ResetDecode();
     mAudioSeekRequest.Begin(GetAudioReader()->Seek(GetReaderAudioTime(mLastAudioTime), 0)
                             ->RefableThen(GetTaskQueue(), __func__, this,
                                           &MediaSourceReader::CompleteAudioSeekAndDoRequest,
                                           &MediaSourceReader::CompleteAudioSeekAndRejectPromise));
     return;
   }
 
   CheckForWaitOrEndOfStream(MediaData::AUDIO_DATA, mLastAudioTime);
@@ -271,16 +273,17 @@ MediaSourceReader::RequestVideoData(bool
     mVideoPromise.Reject(CANCELED, __func__);
     return p;
   }
   MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
 
   SwitchSourceResult ret = SwitchVideoSource(&mLastVideoTime);
   switch (ret) {
     case SOURCE_NEW:
+      GetVideoReader()->ResetDecode();
       mVideoSeekRequest.Begin(GetVideoReader()->Seek(GetReaderVideoTime(mLastVideoTime), 0)
                              ->RefableThen(GetTaskQueue(), __func__, this,
                                            &MediaSourceReader::CompleteVideoSeekAndDoRequest,
                                            &MediaSourceReader::CompleteVideoSeekAndRejectPromise));
       break;
     case SOURCE_NONE:
       if (mLastVideoTime) {
         CheckForWaitOrEndOfStream(MediaData::VIDEO_DATA, mLastVideoTime);
@@ -361,16 +364,17 @@ MediaSourceReader::OnVideoNotDecoded(Not
   // switching to the end of the buffered range.
   MOZ_ASSERT(aReason == END_OF_STREAM);
   if (mVideoSourceDecoder) {
     AdjustEndTime(&mLastVideoTime, mVideoSourceDecoder);
   }
 
   // See if we can find a different reader that can pick up where we left off.
   if (SwitchVideoSource(&mLastVideoTime) == SOURCE_NEW) {
+    GetVideoReader()->ResetDecode();
     mVideoSeekRequest.Begin(GetVideoReader()->Seek(GetReaderVideoTime(mLastVideoTime), 0)
                            ->RefableThen(GetTaskQueue(), __func__, this,
                                          &MediaSourceReader::CompleteVideoSeekAndDoRequest,
                                          &MediaSourceReader::CompleteVideoSeekAndRejectPromise));
     return;
   }
 
   CheckForWaitOrEndOfStream(MediaData::VIDEO_DATA, mLastVideoTime);
@@ -706,17 +710,17 @@ MediaSourceReader::TrackBuffersContainTi
   return true;
 }
 
 void
 MediaSourceReader::NotifyTimeRangesChanged()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mWaitingForSeekData) {
-    //post a task to the state machine thread to call seek.
+    //post a task to the decode queue to try to complete the pending seek.
     RefPtr<nsIRunnable> task(NS_NewRunnableMethod(
         this, &MediaSourceReader::AttemptSeek));
     GetTaskQueue()->Dispatch(task.forget());
   }
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg which is non-MSE */)
@@ -813,16 +817,17 @@ void
 MediaSourceReader::DoAudioSeek()
 {
   if (SwitchAudioSource(&mPendingSeekTime) == SOURCE_NONE) {
     // Data we need got evicted since the last time we checked for data
     // availability. Abort current seek attempt.
     mWaitingForSeekData = true;
     return;
   }
+  GetAudioReader()->ResetDecode();
   mAudioSeekRequest.Begin(GetAudioReader()->Seek(GetReaderAudioTime(mPendingSeekTime), 0)
                          ->RefableThen(GetTaskQueue(), __func__, this,
                                        &MediaSourceReader::OnAudioSeekCompleted,
                                        &MediaSourceReader::OnAudioSeekFailed));
   MSE_DEBUG("reader=%p", GetAudioReader());
 }
 
 void
@@ -856,21 +861,16 @@ MediaSourceReader::AttemptSeek()
     if (!TrackBuffersContainTime(mPendingSeekTime)) {
       mVideoSourceDecoder = nullptr;
       mAudioSourceDecoder = nullptr;
       return;
     }
     mWaitingForSeekData = false;
   }
 
-  ResetDecode();
-  for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
-    mTrackBuffers[i]->ResetDecode();
-  }
-
   // Decoding discontinuity upon seek, reset last times to seek target.
   mLastAudioTime = mPendingSeekTime;
   mLastVideoTime = mPendingSeekTime;
 
   if (mVideoTrack) {
     DoVideoSeek();
   } else if (mAudioTrack) {
     DoAudioSeek();
@@ -883,16 +883,17 @@ void
 MediaSourceReader::DoVideoSeek()
 {
   if (SwitchVideoSource(&mPendingSeekTime) == SOURCE_NONE) {
     // Data we need got evicted since the last time we checked for data
     // availability. Abort current seek attempt.
     mWaitingForSeekData = true;
     return;
   }
+  GetVideoReader()->ResetDecode();
   mVideoSeekRequest.Begin(GetVideoReader()->Seek(GetReaderVideoTime(mPendingSeekTime), 0)
                           ->RefableThen(GetTaskQueue(), __func__, this,
                                         &MediaSourceReader::OnVideoSeekCompleted,
                                         &MediaSourceReader::OnVideoSeekFailed));
   MSE_DEBUG("reader=%p", GetVideoReader());
 }
 
 nsresult
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -831,24 +831,16 @@ TrackBuffer::BreakCycles()
 
   // These are cleared in Shutdown()
   MOZ_ASSERT(!mDecoders.Length());
   MOZ_ASSERT(mInitializedDecoders.IsEmpty());
   MOZ_ASSERT(!mParentDecoder);
 }
 
 void
-TrackBuffer::ResetDecode()
-{
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
-    mDecoders[i]->GetReader()->ResetDecode();
-  }
-}
-
-void
 TrackBuffer::ResetParserState()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mParser->HasInitData() && !mParser->HasCompleteInitData()) {
     // We have an incomplete init segment pending. reset current parser and
     // discard the current decoder.
     mParser = ContainerParser::CreateForMIMEType(mType);
@@ -1002,18 +994,18 @@ TrackBuffer::RangeRemoval(int64_t aStart
 
   if (aStart <= bufferedStart && aEnd < bufferedEnd) {
     // Evict data from beginning.
     for (size_t i = 0; i < decoders.Length(); ++i) {
       nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
       decoders[i]->GetBuffered(buffered);
       if (int64_t(buffered->GetEndTime() * USECS_PER_S) < aEnd) {
         // Can be fully removed.
-        MSE_DEBUG("remove all bufferedEnd=%f time=%f, size=%lld",
-                  buffered->GetEndTime(), time,
+        MSE_DEBUG("remove all bufferedEnd=%f size=%lld",
+                  buffered->GetEndTime(),
                   decoders[i]->GetResource()->GetSize());
         decoders[i]->GetResource()->EvictAll();
       } else {
         int64_t offset = decoders[i]->ConvertToByteOffset(aEnd);
         MSE_DEBUG("removing some bufferedEnd=%f offset=%lld size=%lld",
                   buffered->GetEndTime(), offset,
                   decoders[i]->GetResource()->GetSize());
         if (offset > 0) {
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -76,19 +76,16 @@ public:
   bool IsReady();
 
   // Returns true if any of the decoders managed by this track buffer
   // contain aTime in their buffered ranges.
   bool ContainsTime(int64_t aTime, int64_t aTolerance);
 
   void BreakCycles();
 
-  // Call ResetDecode() on each decoder in mDecoders.
-  void ResetDecode();
-
   // Run MSE Reset Parser State Algorithm.
   // 3.5.2 Reset Parser State
   // http://w3c.github.io/media-source/#sourcebuffer-reset-parser-state
   void ResetParserState();
 
   // Returns a reference to mInitializedDecoders, used by MediaSourceReader
   // to select decoders.
   // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader.
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2790,30 +2790,19 @@ ElementRestyler::Restyle(nsRestyleHint a
     // structs that were swapped out.
     //
     // Much of the time we will not get in here; we do for example when the
     // style context is shared with a later IB split sibling (which we won't
     // restyle until a bit later) or if other code is holding a strong reference
     // to the style context (as is done by nsTransformedTextRun objects, which
     // can be referenced by a text frame's mTextRun longer than the frame's
     // mStyleContext).
-    //
-    // We coalesce entries in mContextsToClear when we detect that the last
-    // style context appended has oldContext as its parent, as
-    // ClearCachedInheritedStyleDataOnDescendants handles a whole subtree
-    // of style contexts.
-    if (!mContextsToClear.IsEmpty() &&
-        mContextsToClear.LastElement().mStyleContext->GetParent() == oldContext &&
-        mContextsToClear.LastElement().mStructs == swappedStructs) {
-      mContextsToClear.LastElement().mStyleContext = Move(oldContext);
-    } else {
-      ContextToClear* toClear = mContextsToClear.AppendElement();
-      toClear->mStyleContext = Move(oldContext);
-      toClear->mStructs = swappedStructs;
-    }
+    ContextToClear* toClear = mContextsToClear.AppendElement();
+    toClear->mStyleContext = Move(oldContext);
+    toClear->mStructs = swappedStructs;
   }
 
   mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
 }
 
 /**
  * Depending on the details of the frame we are restyling or its old style
  * context, we may or may not be able to stop restyling after this frame if
@@ -2976,17 +2965,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
 
   LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
               FrameTagToString(aSelf).get(),
               RestyleManager::RestyleHintToString(aRestyleHint).get());
   LOG_RESTYLE_INDENT();
 
   RestyleResult result;
 
-  if (aRestyleHint || true /* XXX bug 1092363 */) {
+  if (aRestyleHint) {
     result = eRestyleResult_Continue;
   } else {
     result = ComputeRestyleResultFromFrame(aSelf);
   }
 
   nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE;
   nsRefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
   nsStyleSet* styleSet = mPresContext->StyleSet();
@@ -3274,19 +3263,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // which is important to maintain various invariants about
       // frame types matching their style contexts.
       // Note that this check even makes sense if we didn't call
       // CaptureChange because of copyFromContinuation being true,
       // since we'll have copied the existing context from the
       // previous continuation, so newContext == oldContext.
 
       if (result != eRestyleResult_Stop) {
-        if (true) {
-          // XXX bug 1092363
-        } else if (copyFromContinuation) {
+        if (copyFromContinuation) {
           LOG_RESTYLE("not swapping style structs, since we copied from a "
                       "continuation");
         } else if (oldContext->IsShared() && newContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since both old and contexts "
                       "are shared");
         } else if (oldContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the old context is "
                       "shared");
--- a/layout/reftests/printing/reftest.list
+++ b/layout/reftests/printing/reftest.list
@@ -27,9 +27,9 @@ fails-if(B2G) == 115199-1.html 115199-1-
 skip-if(B2G) fuzzy-if(cocoaWidget,1,5000) == 745025-1.html 745025-1-ref.html # reftest-print doesn't work on B2G
 == 820496-1.html 820496-1-ref.html
 
 # NOTE: These tests don't yet rigorously test what they're
 # trying to test (shrink-to-fit behavior), due to bug 967311.
 random-if(B2G&&browserIsRemote) == 960822.html 960822-ref.html # reftest-print doesn't work on B2G (scrollbar difference only)
 == 966419-1.html 966419-1-ref.html
 == 966419-2.html 966419-2-ref.html
-skip-if(B2G) asserts(2) HTTP(..) == 1108104.html 1108104-ref.html # bug 1067755
+skip-if(B2G) asserts(4) HTTP(..) == 1108104.html 1108104-ref.html # bug 1067755
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1136010-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+body { text-transform: uppercase; width: 200px; height: 200px; background-color: white; }
+#a, #b { font-size: 24px; }
+</style>
+<div id=a><div id=b><span>x</span><span>y</span></div></div>
+<script>
+document.body.offsetTop;
+var a = document.getElementById("a");
+var b = document.getElementById("b");
+a.style.fontSize = "24px";
+b.style.fontSize = "24px";
+document.body.offsetTop;
+b.style.fontSize = "36px";
+document.body.offsetTop;
+</script>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -106,10 +106,11 @@ load 945048-1.html
 pref(layers.offmainthreadcomposition.async-animations,true) load 972199-1.html
 load 989965-1.html
 load 992333-1.html
 pref(dom.webcomponents.enabled,true) load 1017798-1.html
 load 1028514-1.html
 load 1066089-1.html
 load 1074651-1.html
 pref(dom.webcomponents.enabled,true) load 1089463-1.html
+load 1136010-1.html
 load large_border_image_width.html
 load border-image-visited-link.html
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -341,16 +341,17 @@ class RemoteReftest(RefTest):
     def stopWebServer(self, options):
         self.server.stop()
 
     def createReftestProfile(self, options, reftestlist):
         profile = RefTest.createReftestProfile(self, options, reftestlist, server=options.remoteWebServer)
         profileDir = profile.profile
 
         prefs = {}
+        prefs["app.update.url.android"] = ""
         prefs["browser.firstrun.show.localepicker"] = False
         prefs["font.size.inflation.emPerLine"] = 0
         prefs["font.size.inflation.minTwips"] = 0
         prefs["reftest.remote"] = True
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
         prefs["reftest.uri"] = "%s" % reftestlist
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -498,24 +498,23 @@ pref("ui.windowframe", "#efebe7");
 
 /* prefs used by the update timer system (including blocklist pings) */
 pref("app.update.timerFirstInterval", 30000); // milliseconds
 pref("app.update.timerMinimumDelay", 30); // seconds
 
 // used by update service to decide whether or not to
 // automatically download an update
 pref("app.update.autodownload", "wifi");
+pref("app.update.url.android", "https://aus4.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%MOZ_VERSION%/update.xml");
 
 #ifdef MOZ_UPDATER
 /* prefs used specifically for updating the app */
 pref("app.update.enabled", false);
 pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
 
-// If you are looking for app.update.url, we no longer use it.
-// See mobile/android/base/updater/UpdateServiceHelper.java
 #endif
 
 // replace newlines with spaces on paste into single-line text boxes
 pref("editor.singleLine.pasteNewlines", 2);
 
 // threshold where a tap becomes a drag, in 1/240" reference pixels
 // The names of the preferences are to be in sync with EventStateManager.cpp
 pref("ui.dragThresholdX", 25);
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -41,17 +41,16 @@ import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.menu.MenuPanel;
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
@@ -652,26 +651,21 @@ public abstract class GeckoApp
 
         } else if ("ToggleChrome:Hide".equals(event)) {
             toggleChrome(false);
 
         } else if ("ToggleChrome:Show".equals(event)) {
             toggleChrome(true);
 
         } else if ("Update:Check".equals(event)) {
-            startService(new Intent(
-                    UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
-
+            UpdateServiceHelper.checkForUpdate(this);
         } else if ("Update:Download".equals(event)) {
-            startService(new Intent(
-                    UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE, null, this, UpdateService.class));
-
+            UpdateServiceHelper.downloadUpdate(this);
         } else if ("Update:Install".equals(event)) {
-            startService(new Intent(
-                    UpdateServiceHelper.ACTION_APPLY_UPDATE, null, this, UpdateService.class));
+            UpdateServiceHelper.applyUpdate(this);
         }
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Gecko:DelayedStartup")) {
                 ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
@@ -1586,35 +1580,31 @@ public abstract class GeckoApp
         mContactService = new ContactService(EventDispatcher.getInstance(), this);
 
         mPromptService = new PromptService(this);
 
         mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.anchor_handle),
                                            (TextSelectionHandle) findViewById(R.id.caret_handle),
                                            (TextSelectionHandle) findViewById(R.id.focus_handle));
 
-        PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
-            @Override public void prefValue(String pref, String value) {
-                UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
-            }
-        });
-
         // Trigger the completion of the telemetry timer that wraps activity startup,
         // then grab the duration to give to FHR.
         mJavaUiStartupTimer.stop();
         final long javaDuration = mJavaUiStartupTimer.getElapsed();
 
         ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
             @Override
             public void run() {
                 final HealthRecorder rec = mHealthRecorder;
                 if (rec != null) {
                     rec.recordJavaStartupTime(javaDuration);
                 }
 
+                UpdateServiceHelper.registerForUpdates(GeckoApp.this);
+
                 // Kick off our background services. We do this by invoking the broadcast
                 // receiver, which uses the system alarm infrastructure to perform tasks at
                 // intervals.
                 GeckoPreferences.broadcastHealthReportUploadPref(GeckoApp.this);
                 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
                     return;
                 }
             }
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -33,16 +33,17 @@ import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
+import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.FloatingHintEditText;
 
 import android.app.ActionBar;
 import android.app.Activity;
@@ -110,16 +111,17 @@ OnSharedPreferenceChangeListener
     // These match keys in resources/xml*/preferences*.xml
     private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
     private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
     private static final String PREFS_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
     private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled";
     private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding";
     private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled";
     private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload";
+    private static final String PREFS_UPDATER_URL = "app.update.url.android";
     private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata";
     private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more";
     private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
     private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
     private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
     private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
     private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
     private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.enabled";
@@ -1052,17 +1054,19 @@ OnSharedPreferenceChangeListener
             // Even though this is a list preference, we don't want to handle it
             // below, so we return here.
             return onLocaleSelected(Locales.getLanguageTag(lastLocale), (String) newValue);
         }
 
         if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
             setCharEncodingState(((String) newValue).equals("true"));
         } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) {
-            UpdateServiceHelper.registerForUpdates(this, (String) newValue);
+            UpdateServiceHelper.setAutoDownloadPolicy(this, UpdateService.AutoDownloadPolicy.get((String) newValue));
+        } else if (PREFS_UPDATER_URL.equals(prefName)) {
+            UpdateServiceHelper.setUpdateUrl(this, (String) newValue);
         } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) {
             // The healthreport pref only lives in Android, so we do not persist
             // to Gecko, but we do broadcast intent to the health report
             // background uploader service, which will start or stop the
             // repeated background upload attempts.
             broadcastHealthReportUploadPref(this, (Boolean) newValue);
         } else if (PREFS_GEO_REPORTING.equals(prefName)) {
             broadcastStumblerPref(this, (Boolean) newValue);
--- a/mobile/android/base/updater/UpdateService.java
+++ b/mobile/android/base/updater/UpdateService.java
@@ -37,16 +37,17 @@ import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Proxy;
 import java.net.ProxySelector;
+import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
 import java.security.MessageDigest;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.List;
 import java.util.TimeZone;
 
@@ -65,29 +66,72 @@ public class UpdateService extends Inten
 
     private static final String PREFS_NAME = "UpdateService";
     private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID";
     private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction";
     private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue";
     private static final String KEY_LAST_FILE_NAME = "UpdateService.lastFileName";
     private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate";
     private static final String KEY_AUTODOWNLOAD_POLICY = "UpdateService.autoDownloadPolicy";
+    private static final String KEY_UPDATE_URL = "UpdateService.updateUrl";
 
     private SharedPreferences mPrefs;
 
     private NotificationManager mNotificationManager;
     private ConnectivityManager mConnectivityManager;
     private Builder mBuilder;
 
     private boolean mDownloading;
     private boolean mCancelDownload;
     private boolean mApplyImmediately;
 
     private CrashHandler mCrashHandler;
 
+    public enum AutoDownloadPolicy {
+        NONE(-1),
+        WIFI(0),
+        DISABLED(1),
+        ENABLED(2);
+
+        public final int value;
+
+        private AutoDownloadPolicy(int value) {
+            this.value = value;
+        }
+
+        private final static AutoDownloadPolicy[] sValues = AutoDownloadPolicy.values();
+
+        public static AutoDownloadPolicy get(int value) {
+            for (AutoDownloadPolicy id: sValues) {
+                if (id.value == value) {
+                    return id;
+                }
+            }
+            return NONE;
+        }
+
+        public static AutoDownloadPolicy get(String name) {
+            for (AutoDownloadPolicy id: sValues) {
+                if (name.equalsIgnoreCase(id.toString())) {
+                    return id;
+                }
+            }
+            return NONE;
+        }
+    }
+
+    private enum CheckUpdateResult {
+        // Keep these in sync with mobile/android/chrome/content/about.xhtml
+        NOT_AVAILABLE,
+        AVAILABLE,
+        DOWNLOADING,
+        DOWNLOADED
+    }
+
+
     public UpdateService() {
         super("updater");
     }
 
     @Override
     public void onCreate () {
         mCrashHandler = CrashHandler.createDefaultCrashHandler(getApplicationContext());
 
@@ -127,21 +171,29 @@ public class UpdateService extends Inten
         }
 
         return Service.START_REDELIVER_INTENT;
     }
 
     @Override
     protected void onHandleIntent (Intent intent) {
         if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) {
-            int policy = intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME, -1);
-            if (policy >= 0) {
+            AutoDownloadPolicy policy = AutoDownloadPolicy.get(
+                intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME,
+                                   AutoDownloadPolicy.NONE.value));
+
+            if (policy != AutoDownloadPolicy.NONE) {
                 setAutoDownloadPolicy(policy);
             }
 
+            String url = intent.getStringExtra(UpdateServiceHelper.EXTRA_UPDATE_URL_NAME);
+            if (url != null) {
+                setUpdateUrl(url);
+            }
+
             registerForUpdates(false);
         } else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
             startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
             // Use this instead for forcing a download from about:fennec
             // startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL);
         } else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) {
             // We always want to do the download and apply it here
             mApplyImmediately = true;
@@ -150,17 +202,17 @@ public class UpdateService extends Inten
             applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME));
         }
     }
 
     private static boolean hasFlag(int flags, int flag) {
         return (flags & flag) == flag;
     }
 
-    private void sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult result) {
+    private void sendCheckUpdateResult(CheckUpdateResult result) {
         Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT);
         resultIntent.putExtra("result", result.toString());
         sendBroadcast(resultIntent);
     }
 
     private int getUpdateInterval(boolean isRetry) {
         int interval;
         if (isRetry) {
@@ -203,51 +255,49 @@ public class UpdateService extends Inten
 
     private void startUpdate(int flags) {
         setLastAttemptDate();
 
         NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
         if (netInfo == null || !netInfo.isConnected()) {
             Log.i(LOGTAG, "not connected to the network");
             registerForUpdates(true);
-            sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
+            sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
             return;
         }
 
         registerForUpdates(false);
 
         UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL));
         boolean haveUpdate = (info != null);
 
         if (!haveUpdate) {
             Log.i(LOGTAG, "no update available");
-            sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
+            sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
             return;
         }
 
         Log.i(LOGTAG, "update available, buildID = " + info.buildID);
 
-        int autoDownloadPolicy = getAutoDownloadPolicy();
-
+        AutoDownloadPolicy policy = getAutoDownloadPolicy();
 
-        /**
-         * We only start a download automatically if one of following criteria are met:
-         *
-         * - We have a FORCE_DOWNLOAD flag passed in
-         * - The preference is set to 'always'
-         * - The preference is set to 'wifi' and we are using a non-metered network (i.e. the user
-         *   is OK with large data transfers occurring)
-         */
+        // We only start a download automatically if one of following criteria are met:
+        //
+        // - We have a FORCE_DOWNLOAD flag passed in
+        // - The preference is set to 'always'
+        // - The preference is set to 'wifi' and we are using a non-metered network (i.e. the user
+        //   is OK with large data transfers occurring)
+        //
         boolean shouldStartDownload = hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) ||
-            autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_ENABLED ||
-            (autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_WIFI && !ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
+            policy == AutoDownloadPolicy.ENABLED ||
+            (policy == AutoDownloadPolicy.WIFI && !ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
 
         if (!shouldStartDownload) {
-            Log.i(LOGTAG, "not initiating automatic update download due to policy " + autoDownloadPolicy);
-            sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.AVAILABLE);
+            Log.i(LOGTAG, "not initiating automatic update download due to policy " + policy.toString());
+            sendCheckUpdateResult(CheckUpdateResult.AVAILABLE);
 
             // We aren't autodownloading here, so prompt to start the update
             Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
 
             Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE);
             notificationIntent.setClass(this, UpdateService.class);
 
             PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -259,24 +309,24 @@ public class UpdateService extends Inten
 
             mNotificationManager.notify(NOTIFICATION_ID, notification);
 
             return;
         }
 
         File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING));
         if (pkg == null) {
-            sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
+            sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
             return;
         }
 
         Log.i(LOGTAG, "have update package at " + pkg);
 
         saveUpdateInfo(info, pkg);
-        sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADED);
+        sendCheckUpdateResult(CheckUpdateResult.DOWNLOADED);
 
         if (mApplyImmediately) {
             applyUpdate(pkg);
         } else {
             // Prompt to apply the update
             Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
 
             Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
@@ -289,37 +339,42 @@ public class UpdateService extends Inten
             notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title),
                                             getResources().getString(R.string.updater_apply_select),
                                             contentIntent);
 
             mNotificationManager.notify(NOTIFICATION_ID, notification);
         }
     }
 
-    private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException {
-        Log.i(LOGTAG, "opening connection with url: " + url);
+    private URLConnection openConnectionWithProxy(URI uri) throws java.net.MalformedURLException, java.io.IOException {
+        Log.i(LOGTAG, "opening connection with URI: " + uri);
 
         ProxySelector ps = ProxySelector.getDefault();
         Proxy proxy = Proxy.NO_PROXY;
         if (ps != null) {
-            List<Proxy> proxies = ps.select(url.toURI());
+            List<Proxy> proxies = ps.select(uri);
             if (proxies != null && !proxies.isEmpty()) {
                 proxy = proxies.get(0);
             }
         }
 
-        return url.openConnection(proxy);
+        return uri.toURL().openConnection(proxy);
     }
 
     private UpdateInfo findUpdate(boolean force) {
         try {
-            URL url = UpdateServiceHelper.getUpdateUrl(this, force);
+            URI uri = getUpdateURI(force);
+
+            if (uri == null) {
+              Log.e(LOGTAG, "failed to get update URI");
+              return null;
+            }
 
             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-            Document dom = builder.parse(openConnectionWithProxy(url).getInputStream());
+            Document dom = builder.parse(openConnectionWithProxy(uri).getInputStream());
 
             NodeList nodes = dom.getElementsByTagName("update");
             if (nodes == null || nodes.getLength() == 0)
                 return null;
 
             Node updateNode = nodes.item(0);
             Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID");
             if (buildIdNode == null)
@@ -337,17 +392,17 @@ public class UpdateService extends Inten
 
             if (urlNode == null || hashFunctionNode == null ||
                 hashValueNode == null || sizeNode == null) {
                 return null;
             }
 
             // Fill in UpdateInfo from the XML data
             UpdateInfo info = new UpdateInfo();
-            info.url = new URL(urlNode.getTextContent());
+            info.uri = new URI(urlNode.getTextContent());
             info.buildID = buildIdNode.getTextContent();
             info.hashFunction = hashFunctionNode.getTextContent();
             info.hashValue = hashValueNode.getTextContent();
 
             try {
                 info.size = Integer.parseInt(sizeNode.getTextContent());
             } catch (NumberFormatException e) {
                 Log.e(LOGTAG, "Failed to find APK size: ", e);
@@ -441,19 +496,27 @@ public class UpdateService extends Inten
 
         pkg.delete();
         Log.i(LOGTAG, "deleted update package: " + path);
 
         return true;
     }
 
     private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) {
+        URL url = null;
+        try {
+            url = info.uri.toURL();
+        } catch (java.net.MalformedURLException e) {
+            Log.e(LOGTAG, "failed to read URL: ", e);
+            return null;
+        }
+
         File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
         path.mkdirs();
-        String fileName = new File(info.url.getFile()).getName();
+        String fileName = new File(url.getFile()).getName();
         File downloadFile = new File(path, fileName);
 
         if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) {
             // The last saved buildID is the same as the one for the current update. We also have a file
             // already downloaded, so it's probably the package we want. Verify it to be sure and just
             // return that if it matches.
 
             if (verifyDownloadedPackage(downloadFile)) {
@@ -461,27 +524,27 @@ public class UpdateService extends Inten
                 return downloadFile;
             } else {
                 // Didn't match, so we're going to download a new one.
                 downloadFile.delete();
             }
         }
 
         Log.i(LOGTAG, "downloading update package");
-        sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADING);
+        sendCheckUpdateResult(CheckUpdateResult.DOWNLOADING);
 
         OutputStream output = null;
         InputStream input = null;
 
         mDownloading = true;
         mCancelDownload = false;
         showDownloadNotification(downloadFile);
 
         try {
-            URLConnection conn = openConnectionWithProxy(info.url);
+            URLConnection conn = openConnectionWithProxy(info.uri);
             int length = conn.getContentLength();
 
             output = new BufferedOutputStream(new FileOutputStream(downloadFile));
             input = new BufferedInputStream(conn.getInputStream());
 
             byte[] buf = new byte[BUFSIZE];
             int len = 0;
 
@@ -620,49 +683,59 @@ public class UpdateService extends Inten
     }
 
     private void setLastAttemptDate() {
         SharedPreferences.Editor editor = mPrefs.edit();
         editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis());
         editor.commit();
     }
 
-    private int getAutoDownloadPolicy() {
-        return mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, UpdateServiceHelper.AUTODOWNLOAD_WIFI);
+    private AutoDownloadPolicy getAutoDownloadPolicy() {
+        return AutoDownloadPolicy.get(mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, AutoDownloadPolicy.WIFI.value));
     }
 
-    private void setAutoDownloadPolicy(int policy) {
+    private void setAutoDownloadPolicy(AutoDownloadPolicy policy) {
         SharedPreferences.Editor editor = mPrefs.edit();
-        editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy);
+        editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy.value);
+        editor.commit();
+    }
+
+    private URI getUpdateURI(boolean force) {
+        return UpdateServiceHelper.expandUpdateURI(this, mPrefs.getString(KEY_UPDATE_URL, null), force);
+    }
+
+    private void setUpdateUrl(String url) {
+        SharedPreferences.Editor editor = mPrefs.edit();
+        editor.putString(KEY_UPDATE_URL, url);
         editor.commit();
     }
 
     private void saveUpdateInfo(UpdateInfo info, File downloaded) {
         SharedPreferences.Editor editor = mPrefs.edit();
         editor.putString(KEY_LAST_BUILDID, info.buildID);
         editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction);
         editor.putString(KEY_LAST_HASH_VALUE, info.hashValue);
         editor.putString(KEY_LAST_FILE_NAME, downloaded.toString());
         editor.commit();
     }
 
     private class UpdateInfo {
-        public URL url;
+        public URI uri;
         public String buildID;
         public String hashFunction;
         public String hashValue;
         public int size;
 
         private boolean isNonEmpty(String s) {
             return s != null && s.length() > 0;
         }
 
         public boolean isValid() {
-            return url != null && isNonEmpty(buildID) &&
+            return uri != null && isNonEmpty(buildID) &&
                 isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0;
         }
 
         @Override
         public String toString() {
-            return "url = " + url + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size;
+            return "uri = " + uri + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size;
         }
     }
 }
--- a/mobile/android/base/updater/UpdateServiceHelper.java
+++ b/mobile/android/base/updater/UpdateServiceHelper.java
@@ -1,151 +1,212 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.updater;
 
 import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.GeckoJarReader;
 
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.util.Log;
 
-import java.net.URL;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
 
 public class UpdateServiceHelper {
     public static final String ACTION_REGISTER_FOR_UPDATES = AppConstants.ANDROID_PACKAGE_NAME + ".REGISTER_FOR_UPDATES";
     public static final String ACTION_UNREGISTER_FOR_UPDATES = AppConstants.ANDROID_PACKAGE_NAME + ".UNREGISTER_FOR_UPDATES";
     public static final String ACTION_CHECK_FOR_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".CHECK_FOR_UPDATE";
     public static final String ACTION_CHECK_UPDATE_RESULT = AppConstants.ANDROID_PACKAGE_NAME + ".CHECK_UPDATE_RESULT";
     public static final String ACTION_DOWNLOAD_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".DOWNLOAD_UPDATE";
     public static final String ACTION_APPLY_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".APPLY_UPDATE";
     public static final String ACTION_CANCEL_DOWNLOAD = AppConstants.ANDROID_PACKAGE_NAME + ".CANCEL_DOWNLOAD";
 
     // Flags for ACTION_CHECK_FOR_UPDATE
-    public static final int FLAG_FORCE_DOWNLOAD = 1;
-    public static final int FLAG_OVERWRITE_EXISTING = 1 << 1;
-    public static final int FLAG_REINSTALL = 1 << 2;
-    public static final int FLAG_RETRY = 1 << 3;
+    protected static final int FLAG_FORCE_DOWNLOAD = 1;
+    protected static final int FLAG_OVERWRITE_EXISTING = 1 << 1;
+    protected static final int FLAG_REINSTALL = 1 << 2;
+    protected static final int FLAG_RETRY = 1 << 3;
 
     // Name of the Intent extra for the autodownload policy, used with ACTION_REGISTER_FOR_UPDATES
-    public static final String EXTRA_AUTODOWNLOAD_NAME = "autodownload";
-
-    // Values for EXTRA_AUTODOWNLOAD_NAME
-    public static final int AUTODOWNLOAD_WIFI = 0;
-    public static final int AUTODOWNLOAD_DISABLED = 1;
-    public static final int AUTODOWNLOAD_ENABLED = 2;
+    protected static final String EXTRA_AUTODOWNLOAD_NAME = "autodownload";
 
     // Name of the Intent extra that holds the flags for ACTION_CHECK_FOR_UPDATE
-    public static final String EXTRA_UPDATE_FLAGS_NAME = "updateFlags";
+    protected static final String EXTRA_UPDATE_FLAGS_NAME = "updateFlags";
 
     // Name of the Intent extra that holds the APK path, used with ACTION_APPLY_UPDATE
-    public static final String EXTRA_PACKAGE_PATH_NAME = "packagePath";
+    protected static final String EXTRA_PACKAGE_PATH_NAME = "packagePath";
+
+    // Name of the Intent extra for the update URL, used with ACTION_REGISTER_FOR_UPDATES
+    protected static final String EXTRA_UPDATE_URL_NAME = "updateUrl";
 
     private static final String LOGTAG = "UpdateServiceHelper";
     private static final String DEFAULT_UPDATE_LOCALE = "en-US";
 
-    private static final String UPDATE_URL;
-
     // So that updates can be disabled by tests.
     private static volatile boolean isEnabled = true;
 
-    static {
-        final String pkgSpecial;
-        if (AppConstants.MOZ_PKG_SPECIAL != null) {
-            pkgSpecial = "-" + AppConstants.MOZ_PKG_SPECIAL;
-        } else {
-            pkgSpecial = "";
+    private enum Pref {
+        AUTO_DOWNLOAD_POLICY("app.update.autodownload"),
+        UPDATE_URL("app.update.url.android");
+
+        public final String name;
+
+        private Pref(String name) {
+            this.name = name;
         }
-        UPDATE_URL = "https://aus4.mozilla.org/update/4/" + AppConstants.MOZ_APP_BASENAME + "/" +
-                     AppConstants.MOZ_APP_VERSION         +
-                     "/%BUILDID%/Android_"                + AppConstants.MOZ_APP_ABI + pkgSpecial +
-                     "/%LOCALE%/"                         + AppConstants.MOZ_UPDATE_CHANNEL +
-                     "/%OS_VERSION%/default/default/"     + AppConstants.MOZILLA_VERSION +
-                     "/update.xml";
-    }
+
+        public final static String[] names;
+
+        @Override
+        public String toString() {
+            return this.name;
+        }
 
-    public enum CheckUpdateResult {
-        // Keep these in sync with mobile/android/chrome/content/about.xhtml
-        NOT_AVAILABLE,
-        AVAILABLE,
-        DOWNLOADING,
-        DOWNLOADED
+        static {
+            ArrayList<String> nameList = new ArrayList<String>();
+
+            for (Pref id: Pref.values()) {
+                nameList.add(id.toString());
+            }
+
+            names = nameList.toArray(new String[0]);
+        }
     }
 
     @RobocopTarget
     public static void setEnabled(final boolean enabled) {
         isEnabled = enabled;
     }
 
-    public static URL getUpdateUrl(Context context, boolean force) {
+    public static URI expandUpdateURI(Context context, String updateUri, boolean force) {
+        if (updateUri == null) {
+            return null;
+        }
+
         PackageManager pm = context.getPackageManager();
 
-        String locale = null;
+        String pkgSpecial = AppConstants.MOZ_PKG_SPECIAL != null ?
+                            "-" + AppConstants.MOZ_PKG_SPECIAL :
+                            "";
+        String locale = DEFAULT_UPDATE_LOCALE;
+
         try {
             ApplicationInfo info = pm.getApplicationInfo(AppConstants.ANDROID_PACKAGE_NAME, 0);
             String updateLocaleUrl = "jar:jar:file://" + info.sourceDir + "!/" + AppConstants.OMNIJAR_NAME + "!/update.locale";
 
-            locale = GeckoJarReader.getText(updateLocaleUrl);
-
-            if (locale != null)
-                locale = locale.trim();
-            else
-                locale = DEFAULT_UPDATE_LOCALE;
+            final String jarLocale = GeckoJarReader.getText(updateLocaleUrl);
+            if (jarLocale != null) {
+                locale = jarLocale.trim();
+            }
         } catch (android.content.pm.PackageManager.NameNotFoundException e) {
             // Shouldn't really be possible, but fallback to default locale
-            Log.i(LOGTAG, "Failed to read update locale file, falling back to " + DEFAULT_UPDATE_LOCALE);
-            locale = DEFAULT_UPDATE_LOCALE;
+            Log.i(LOGTAG, "Failed to read update locale file, falling back to " + locale);
         }
 
-        String url = UPDATE_URL.replace("%LOCALE%", locale).
-            replace("%OS_VERSION%", Build.VERSION.RELEASE).
-            replace("%BUILDID%", force ? "0" : AppConstants.MOZ_APP_BUILDID);
+        String url = updateUri.replace("%PRODUCT%", AppConstants.MOZ_APP_BASENAME)
+            .replace("%VERSION%", AppConstants.MOZ_APP_VERSION)
+            .replace("%BUILD_ID%", force ? "0" : AppConstants.MOZ_APP_BUILDID)
+            .replace("%BUILD_TARGET%", "Android_" + AppConstants.MOZ_APP_ABI + pkgSpecial)
+            .replace("%LOCALE%", locale)
+            .replace("%CHANNEL%", AppConstants.MOZ_UPDATE_CHANNEL)
+            .replace("%OS_VERSION%", Build.VERSION.RELEASE)
+            .replace("%DISTRIBUTION%", "default")
+            .replace("%DISTRIBUTION_VERSION%", "default")
+            .replace("%MOZ_VERSION%", AppConstants.MOZILLA_VERSION);
 
         try {
-            return new URL(url);
-        } catch (java.net.MalformedURLException e) {
+            return new URI(url);
+        } catch (java.net.URISyntaxException e) {
             Log.e(LOGTAG, "Failed to create update url: ", e);
             return null;
         }
     }
 
     public static boolean isUpdaterEnabled() {
         return AppConstants.MOZ_UPDATER && isEnabled;
     }
 
-    public static void registerForUpdates(Context context, String policy) {
-        if (policy == null)
+    public static void setUpdateUrl(Context context, String url) {
+        registerForUpdates(context, null, url);
+    }
+
+    public static void setAutoDownloadPolicy(Context context, UpdateService.AutoDownloadPolicy policy) {
+        registerForUpdates(context, policy, null);
+    }
+
+    public static void checkForUpdate(Context context) {
+        if (context == null) {
             return;
+        }
 
-        int intPolicy;
-        if (policy.equals("wifi")) {
-            intPolicy = AUTODOWNLOAD_WIFI;
-        } else if (policy.equals("disabled")) {
-            intPolicy = AUTODOWNLOAD_DISABLED;
-        } else if (policy.equals("enabled")) {
-            intPolicy = AUTODOWNLOAD_ENABLED;
-        } else {
-            Log.w(LOGTAG, "Unhandled autoupdate policy: " + policy);
+        context.startService(createIntent(context, ACTION_CHECK_FOR_UPDATE));
+    }
+
+    public static void downloadUpdate(Context context) {
+        if (context == null) {
+            return;
+        }
+
+        context.startService(createIntent(context, ACTION_DOWNLOAD_UPDATE));
+    }
+
+    public static void applyUpdate(Context context) {
+        if (context == null) {
             return;
         }
 
-        registerForUpdates(context, intPolicy);
+        context.startService(createIntent(context, ACTION_APPLY_UPDATE));
     }
 
-    // 'policy' should one of AUTODOWNLOAD_WIFI, AUTODOWNLOAD_DISABLED, AUTODOWNLOAD_ENABLED
-    public static void registerForUpdates(Context context, int policy) {
-        if (!isUpdaterEnabled())
-            return;
+    public static void registerForUpdates(final Context context) {
+        if (!isUpdaterEnabled()) {
+             return;
+        }
+
+        final HashMap<String, Object> prefs = new HashMap<String, Object>();
+
+        PrefsHelper.getPrefs(Pref.names, new PrefsHelper.PrefHandlerBase() {
+            @Override public void prefValue(String pref, String value) {
+                prefs.put(pref, value);
+            }
 
-        Intent intent = new Intent(UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES, null, context, UpdateService.class);
-        intent.putExtra(EXTRA_AUTODOWNLOAD_NAME, policy);
+            @Override public void finish() {
+                UpdateServiceHelper.registerForUpdates(context,
+                    UpdateService.AutoDownloadPolicy.get(
+                        (String) prefs.get(Pref.AUTO_DOWNLOAD_POLICY.toString())),
+                      (String) prefs.get(Pref.UPDATE_URL.toString()));
+            }
+        });
+    }
+
+    public static void registerForUpdates(Context context, UpdateService.AutoDownloadPolicy policy, String url) {
+        if (!isUpdaterEnabled()) {
+             return;
+        }
+
+        Intent intent = createIntent(context, ACTION_REGISTER_FOR_UPDATES);
+
+        if (policy != null) {
+            intent.putExtra(EXTRA_AUTODOWNLOAD_NAME, policy.value);
+        }
+
+        if (url != null) {
+            intent.putExtra(EXTRA_UPDATE_URL_NAME, url);
+        }
 
         context.startService(intent);
     }
+
+    private static Intent createIntent(Context context, String action) {
+        return new Intent(action, null, context, UpdateService.class);
+    }
 }
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -739,17 +739,21 @@ FxAccountsInternal.prototype = {
         if (data && !this.isUserEmailVerified(data)) {
           this.pollEmailStatus(currentState, data.sessionToken, "start");
         }
         return data;
       });
   },
 
   startVerifiedCheck: function(data) {
-    log.debug("startVerifiedCheck " + JSON.stringify(data));
+    log.debug("startVerifiedCheck", data && data.verified);
+    if (logPII) {
+      log.debug("startVerifiedCheck with user data", data);
+    }
+
     // Get us to the verified state, then get the keys. This returns a promise
     // that will fire when we are completely ready.
     //
     // Login is truly complete once keys have been fetched, so once getKeys()
     // obtains and stores kA and kB, it will fire the onverified observer
     // notification.
 
     // The callers of startVerifiedCheck never consume a returned promise (ie,
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -36,16 +36,17 @@ user_pref("dom.min_background_timeout_va
 user_pref("test.mousescroll", true);
 user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
 user_pref("network.http.prompt-temp-redirect", false);
 user_pref("media.cache_size", 1000);
 user_pref("media.volume_scale", "0.01");
 user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
 user_pref("app.update.staging.enabled", false);
+user_pref("app.update.url.android", "");
 // Make sure GMPInstallManager won't hit the network.
 user_pref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml");
 user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
 user_pref("dom.w3c_touch_events.enabled", 1);
 user_pref("dom.undo_manager.enabled", true);
 user_pref("dom.webcomponents.enabled", true);
 user_pref("dom.animations-api.core.enabled", true);
 // Set a future policy version to avoid the telemetry prompt.