Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 15 Jan 2018 11:57:47 +0200
changeset 399265 b7d66e4e60ef177ec9ae687daa29443ae4a2acfc
parent 399264 6f9b763bb1c9f03be9ff0420a7b52261306bd227 (diff)
parent 399243 52397f10a40b2dc2c2e07440e1b9c986eec112fa (current diff)
child 399266 9c060daf95f04901e8d29501947929b0ac6adc38
child 399288 ab6c457eac94fed47844d470aa18ecbfa8da0cfc
child 399297 a00e3a3dbad43759ee9474d6c7c908fa16deaee8
push id98924
push userrgurzau@mozilla.com
push dateMon, 15 Jan 2018 10:01:21 +0000
treeherdermozilla-inbound@9c060daf95f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone59.0a1
first release with
nightly linux32
b7d66e4e60ef / 59.0a1 / 20180115100056 / files
nightly linux64
b7d66e4e60ef / 59.0a1 / 20180115100056 / files
nightly mac
b7d66e4e60ef / 59.0a1 / 20180115100056 / files
nightly win32
b7d66e4e60ef / 59.0a1 / 20180115100056 / files
nightly win64
b7d66e4e60ef / 59.0a1 / 20180115100056 / 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 mozilla-central. a=merge
dom/base/nsDocument.cpp
dom/tests/mochitest/webcomponents/test_bug1176757.html
testing/web-platform/meta/shadow-dom/Node-prototype-cloneNode.html.ini
testing/web-platform/meta/shadow-dom/ShadowRoot-interface.html.ini
testing/web-platform/meta/shadow-dom/untriaged/elements-and-dom-objects/shadowroot-object/shadowroot-attributes/activeElement-confirm-return-null.html.ini
testing/web-platform/meta/shadow-dom/untriaged/elements-and-dom-objects/shadowroot-object/shadowroot-attributes/test-007.html.ini
testing/web-platform/meta/shadow-dom/untriaged/elements-and-dom-objects/shadowroot-object/shadowroot-methods/test-010.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/active-element/test-001.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/active-element/test-002.html.ini
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13167,16 +13167,45 @@ nsIDocument::SetUserHasInteracted(bool a
   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
           ("Document %p has been interacted by user.", this));
   mUserHasInteracted = aUserHasInteracted;
 }
 
 void
 nsIDocument::NotifyUserActivation()
 {
+  ActivateByUserGesture();
+  // Activate parent document which has same principle on the parent chain.
+  nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+  nsCOMPtr<nsIDocument> parent = GetSameTypeParentDocument();
+  while (parent) {
+    parent->MaybeActivateByUserGesture(principal);
+    parent = parent->GetSameTypeParentDocument();
+  }
+}
+
+void
+nsIDocument::MaybeActivateByUserGesture(nsIPrincipal* aPrincipal)
+{
+  bool isEqual = false;
+  nsresult rv = aPrincipal->Equals(NodePrincipal(), &isEqual);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // If a child frame is actived, it would always activate the top frame and its
+  // parent frames which has same priciple.
+  if (isEqual || IsTopLevelContentDocument()) {
+    ActivateByUserGesture();
+  }
+}
+
+void
+nsIDocument::ActivateByUserGesture()
+{
   if (mUserHasActivatedInteraction) {
     return;
   }
 
   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
           ("Document %p has been activated by user.", this));
   mUserHasActivatedInteraction = true;
 }
@@ -13196,38 +13225,37 @@ nsIDocument::HasBeenUserActivated()
 
   return mUserHasActivatedInteraction;
 }
 
 nsIDocument*
 nsIDocument::GetFirstParentDocumentWithSamePrincipal(nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(aPrincipal);
-  nsIDocument* parent = GetSameTypeParentDocument(this);
+  nsIDocument* parent = GetSameTypeParentDocument();
   while (parent) {
     bool isEqual = false;
     nsresult rv = aPrincipal->Equals(parent->NodePrincipal(), &isEqual);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return nullptr;
     }
 
     if (isEqual) {
       return parent;
     }
-    parent = GetSameTypeParentDocument(parent);
+    parent = parent->GetSameTypeParentDocument();
   }
   MOZ_ASSERT(!parent);
   return nullptr;
 }
 
 nsIDocument*
-nsIDocument::GetSameTypeParentDocument(const nsIDocument* aDoc)
-{
-  MOZ_ASSERT(aDoc);
-  nsCOMPtr<nsIDocShellTreeItem> current = aDoc->GetDocShell();
+nsIDocument::GetSameTypeParentDocument()
+{
+  nsCOMPtr<nsIDocShellTreeItem> current = GetDocShell();
   if (!current) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> parent;
   current->GetSameTypeParent(getter_AddRefs(parent));
   if (!parent) {
     return nullptr;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3066,26 +3066,23 @@ public:
   }
 
   void SetUserHasInteracted(bool aUserHasInteracted);
   bool UserHasInteracted()
   {
     return mUserHasInteracted;
   }
 
-  // This would be called when document get activated by specific user gestures.
+  // This would be called when document get activated by specific user gestures
+  // and propagate the user activation flag to its parent.
   void NotifyUserActivation();
 
   // Return true if document has interacted by specific user gestures.
   bool HasBeenUserActivated();
 
-  // Return the first parent document with same pricipal, return nullptr if we
-  // can't find it.
-  nsIDocument* GetFirstParentDocumentWithSamePrincipal(nsIPrincipal* aPrincipal);
-
   bool HasScriptsBlockedBySandbox();
 
   bool InlineScriptAllowedByCSP();
 
   void ReportHasScrollLinkedEffect();
   bool HasScrollLinkedEffect() const
   {
     return mHasScrollLinkedEffect;
@@ -3266,17 +3263,25 @@ protected:
   // mPresShell is becoming null; in that case it will be used to get hold of
   // the relevant refresh driver.
   void UpdateFrameRequestCallbackSchedulingState(nsIPresShell* aOldShell = nullptr);
 
   // Helper for GetScrollingElement/IsScrollingElement.
   bool IsPotentiallyScrollable(mozilla::dom::HTMLBodyElement* aBody);
 
   // Return the same type parent docuement if exists, or return null.
-  nsIDocument* GetSameTypeParentDocument(const nsIDocument* aDoc);
+  nsIDocument* GetSameTypeParentDocument();
+
+  // Return the first parent document with same pricipal, return nullptr if we
+  // can't find it.
+  nsIDocument* GetFirstParentDocumentWithSamePrincipal(nsIPrincipal* aPrincipal);
+
+  // Activate the flag 'mUserHasActivatedInteraction' by specific user gestures.
+  void ActivateByUserGesture();
+  void MaybeActivateByUserGesture(nsIPrincipal* aPrincipal);
 
   // Helpers for GetElementsByName.
   static bool MatchNameAttribute(mozilla::dom::Element* aElement,
                                  int32_t aNamespaceID,
                                  nsAtom* aAtom, void* aData);
   static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
 
   nsCString mReferrer;
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -917,25 +917,16 @@ EventStateManager::NotifyTargetUserActiv
   if (!doc || doc->HasBeenUserActivated()) {
     return;
   }
 
   MOZ_ASSERT(aEvent->mMessage == eKeyUp   ||
              aEvent->mMessage == eMouseUp ||
              aEvent->mMessage == eTouchEnd);
   doc->NotifyUserActivation();
-
-  // Activate parent document which has same principle on the parent chain.
-  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
-  nsCOMPtr<nsIDocument> parent =
-    doc->GetFirstParentDocumentWithSamePrincipal(principal);
-  while (parent) {
-    parent->NotifyUserActivation();
-    parent = parent->GetFirstParentDocumentWithSamePrincipal(principal);
-  }
 }
 
 void
 EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
 {
   switch (aEvent->mMessage) {
     case eQuerySelectedText:
     case eQueryTextContent:
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -356,17 +356,17 @@ MediaEngineCameraVideoSource::ChooseCapa
       found = true;
       break;
     }
   }
   if (!found) {
     GetCapability(candidateSet[0].mIndex, aCapability);
   }
 
-  LogCapability("Chosen capability", mCapability, sameDistance);
+  LogCapability("Chosen capability", aCapability, sameDistance);
   return true;
 }
 
 void
 MediaEngineCameraVideoSource::SetName(nsString aName)
 {
   mDeviceName = aName;
   bool hasFacingMode = false;
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -293,20 +293,22 @@ MediaEngineRemoteVideoSource::UpdateSing
     const MediaEnginePrefs& aPrefs,
     const nsString& aDeviceId,
     const char** aOutBadConstraint)
 {
   switch (mState) {
     case kReleased:
       MOZ_ASSERT(aHandle);
       mHandleId = aHandle->mId;
+      LOG(("ChooseCapability(kFitness) for mTargetCapability and mCapability ++"));
       if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability, kFitness)) {
         *aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
         return NS_ERROR_FAILURE;
       }
+      LOG(("ChooseCapability(kFitness) for mTargetCapability and mCapability --"));
       mTargetCapability = mCapability;
 
       if (camera::GetChildAndCall(&camera::CamerasChild::AllocateCaptureDevice,
                                   mCapEngine, GetUUID().get(),
                                   kMaxUniqueIdLength, mCaptureIndex,
                                   aHandle->mPrincipalInfo)) {
         return NS_ERROR_FAILURE;
       }
@@ -318,36 +320,40 @@ MediaEngineRemoteVideoSource::UpdateSing
     case kStarted:
       {
         size_t index = mHandleIds.NoIndex;
         if (aHandle) {
           mHandleId = aHandle->mId;
           index = mHandleIds.IndexOf(mHandleId);
         }
 
+        LOG(("ChooseCapability(kFitness) for mTargetCapability ++"));
         if (!ChooseCapability(aNewConstraint, aPrefs, aDeviceId, mTargetCapability,
                               kFitness)) {
           *aOutBadConstraint = FindBadConstraint(aNewConstraint, *this, aDeviceId);
           return NS_ERROR_FAILURE;
         }
+        LOG(("ChooseCapability(kFitness) for mTargetCapability --"));
 
         if (index != mHandleIds.NoIndex) {
           MonitorAutoLock lock(mMonitor);
           mTargetCapabilities[index] = mTargetCapability;
           MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
           MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
           MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
           MOZ_ASSERT(mSources.Length() == mImages.Length());
         }
 
+        LOG(("ChooseCapability(kFeasibility) for mCapability ++"));
         if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability,
                               kFeasibility)) {
           *aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
           return NS_ERROR_FAILURE;
         }
+        LOG(("ChooseCapability(kFeasibility) for mCapability --"));
 
         if (mCapability != mLastCapability) {
           camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
                                   mCapEngine, mCaptureIndex);
           if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
                                       mCapEngine, mCaptureIndex, mCapability,
                                       this)) {
             LOG(("StartCapture failed"));
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -440,16 +440,23 @@ partial interface Document {
 };
 
 // Extension to give chrome JS the ability to determine whether
 // the user has interacted with the document or not.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean userHasInteracted;
 };
 
+// Extension to give chrome JS the ability to simulate activate the docuement
+// by user gesture.
+partial interface Document {
+  [ChromeOnly]
+  void notifyUserActivation();
+};
+
 // Extension to give chrome and XBL JS the ability to determine whether
 // the document is sandboxed without permission to run scripts
 // and whether inline scripts are blocked by the document's CSP.
 partial interface Document {
   [Func="IsChromeOrXBL"] readonly attribute boolean hasScriptsBlockedBySandbox;
   [Func="IsChromeOrXBL"] readonly attribute boolean inlineScriptAllowedByCSP;
 };
 
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -1322,27 +1322,16 @@ AC_LANG_C
 dnl ========================================================
 dnl =  Internationalization checks
 dnl ========================================================
 dnl
 dnl Internationalization and Locale support is different
 dnl on various UNIX platforms.  Checks for specific i18n
 dnl features go here.
 
-dnl check for LC_MESSAGES
-AC_CACHE_CHECK(for LC_MESSAGES,
-		ac_cv_i18n_lc_messages,
-		[AC_TRY_COMPILE([#include <locale.h>],
-				[int category = LC_MESSAGES;],
-				ac_cv_i18n_lc_messages=yes,
-				ac_cv_i18n_lc_messages=no)])
-if test "$ac_cv_i18n_lc_messages" = yes; then
-   AC_DEFINE(HAVE_I18N_LC_MESSAGES)
-fi
-
 AC_HAVE_FUNCS(localeconv)
 fi # ! SKIP_COMPILER_CHECKS
 
 TARGET_XPCOM_ABI=
 if test -n "${CPU_ARCH}" -a -n "${TARGET_COMPILER_ABI}"; then
     TARGET_XPCOM_ABI="${CPU_ARCH}-${TARGET_COMPILER_ABI}"
 fi
 
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1415416-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1415416
+-->
+<head>
+<title>Test for Bug 1415416</title>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function runTest()
+{
+  sendKey("TAB");
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="runTest()">
+<input id = "text1" type="text" autofocus value=""/>
+<input id = "text2" type="text" value=""/>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1415416.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1415416
+-->
+<head>
+<title>Test for Bug 1415416</title>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function runTest() {
+  var text = document.getElementById("text1");
+  text.readOnly = false;
+  text.value = "";
+
+  text = document.getElementById("text2");
+  text.readOnly = false;
+  text.value = "";
+
+  sendKey("TAB");
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="runTest()">
+<input id = "text1" type="text" readonly autofocus value="A" />
+<input id = "text2" type="text" readonly value="B"/>
+</body>
+</html>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -302,16 +302,18 @@ support-files =
   bug1354478-4.html
   bug1354478-4-ref.html
   bug1354478-5.html
   bug1354478-5-ref.html
   bug1354478-6.html
   bug1354478-6-ref.html
   bug1359411.html
   bug1359411-ref.html
+  bug1415416.html
+  bug1415416-ref.html
   image_rgrg-256x256.png
   input-invalid-ref.html
   input-maxlength-invalid-change.html
   input-maxlength-ui-invalid-change.html
   input-maxlength-ui-valid-change.html
   input-maxlength-valid-before-change.html
   input-maxlength-valid-change.html
   input-minlength-invalid-change.html
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -188,16 +188,17 @@ var tests = [
     [ 'bug1263357-5.html' , 'bug1263357-5-ref.html'] ,
     [ 'bug1354478-1.html' , 'bug1354478-1-ref.html'] ,
     [ 'bug1354478-2.html' , 'bug1354478-2-ref.html'] ,
     [ 'bug1354478-3.html' , 'bug1354478-3-ref.html'] ,
     [ 'bug1354478-4.html' , 'bug1354478-4-ref.html'] ,
     [ 'bug1354478-5.html' , 'bug1354478-5-ref.html'] ,
     [ 'bug1354478-6.html' , 'bug1354478-6-ref.html'] ,
     [ 'bug1359411.html'   , 'bug1359411-ref.html' ] ,
+    [ 'bug1415416.html'   , 'bug1415416-ref.html' ] ,
     function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
 ];
 
 if (navigator.appVersion.indexOf("Android") == -1) {
   tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
   tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
   tests.push([ 'bug923376.html'   , 'bug923376-ref.html'   ]);
   tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);});
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -947,16 +947,19 @@ nsTextControlFrame::SelectAllOrCollapseT
   if (numChildren > 0) {
     // We never want to place the selection after the last
     // br under the root node!
     nsIContent *child = rootContent->GetLastChild();
     if (child) {
       if (child->IsHTMLElement(nsGkAtoms::br)) {
         child = child->GetPreviousSibling();
         --numChildren;
+      } else if (child->IsNodeOfType(nsINode::eTEXT) && !child->Length()) {
+        // Editor won't remove text node when empty value.
+        --numChildren;
       }
     }
     if (!aSelect && numChildren) {
       child = child->GetPreviousSibling();
       if (child && child->IsNodeOfType(nsINode::eTEXT)) {
         rootNode = do_QueryInterface(child);
         const nsTextFragment* fragment = child->GetText();
         numChildren = fragment ? fragment->GetLength() : 0;
--- a/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
@@ -243,21 +243,23 @@ public class VideoCaptureAndroid impleme
       averageDurationMs = 1000000.0f / (max_mfps / frameDropRatio);
       camera.startPreview();
       exchange(result, true);
       return;
     } catch (RuntimeException e) {
       error = e;
     }
     Log.e(TAG, "startCapture failed", error);
-    if (camera != null) {
-      Exchanger<Boolean> resultDropper = new Exchanger<Boolean>();
-      stopCaptureOnCameraThread(resultDropper);
-      exchange(resultDropper, false);
-    }
+    // For some devices, camera.setParameters(parameters) would throw
+    // an exception when a specific resolution is set. Originally,
+    // stopCaptureOnCameraThread() is called here to clear up the state.
+    // However, stopCaptureOnCameraThread(), which uses Exchanger to
+    // synchronize and swap data with MediaManager thread, is supposed to be
+    // called by MediaManager thread like we did at stopCapture(). Calling
+    // this function directly in CameraThread will cause deadlock.
     exchange(result, false);
     return;
   }
 
   // Called by native code.  Returns true when camera is known to be stopped.
   @WebRTCJNITarget
   private synchronized boolean stopCapture() {
     Log.d(TAG, "stopCapture");
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -175,29 +175,36 @@ android.libraryVariants.all { variant ->
     // Javadoc and Sources JAR configuration cribbed from
     // https://github.com/mapbox/mapbox-gl-native/blob/d169ea55c1cfa85cd8bf19f94c5f023569f71810/platform/android/MapboxGLAndroidSDK/build.gradle#L85
     // informed by
     // https://code.tutsplus.com/tutorials/creating-and-publishing-an-android-library--cms-24582,
     // and amended from numerous Stackoverflow posts.
     def name = variant.name
     def javadoc = task "javadoc${name.capitalize()}"(type: Javadoc) {
         description = "Generate Javadoc for build variant $name"
-        failOnError = false
         destinationDir = new File(destinationDir, variant.baseName)
+        classpath = files(variant.javaCompile.classpath.files)
+
         source = files(variant.javaCompile.source)
-        classpath = files(variant.javaCompile.classpath.files) + files(android.bootClasspath)
-        options.windowTitle("Mozilla GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API Reference")
-        options.docTitle("Mozilla GeckoView ${mozconfig.substs.MOZ_APP_VERSION}")
-        options.header("Mozilla GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API Reference")
-        options.bottom("&copy; 2016 Mozilla. All rights reserved.")
-        options.links("http://docs.oracle.com/javase/7/docs/api/")
-        options.linksOffline("http://d.android.com/reference/", "$System.env.ANDROID_HOME/docs/reference")
-        // TODO: options.overview("src/main/java/overview.html")
-        options.group("Mozilla GeckoView", "org.mozilla.gecko*") // TODO: narrow this down.
-        exclude '**/R.java', '**/BuildConfig.java', 'com/googlecode/**'
+        exclude '**/R.java', '**/BuildConfig.java', 'com/google/**', 'org/webrtc/**'
+        options.addPathOption('sourcepath', ':').setValue(
+            variant.sourceSets.collect({ it.javaDirectories }).flatten() +
+            variant.generateBuildConfig.sourceOutputDir)
+
+        // javadoc 8 has a bug that requires the rt.jar file from the JRE to be
+        // in the bootclasspath (https://stackoverflow.com/a/30458820).
+        options.bootClasspath = [
+            file("${System.properties['java.home']}/lib/rt.jar")] + android.bootClasspath
+        options.memberLevel = JavadocMemberLevel.PROTECTED
+        options.source = 7
+
+        options.docTitle = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
+        options.header = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
+        options.addStringOption('noindex');
+        options.addStringOption('noqualifier', 'java.lang');
     }
 
     task "javadocJar${name.capitalize()}"(type: Jar, dependsOn: javadoc) {
         classifier = 'javadoc'
         from javadoc.destinationDir
     }
 
     task "sourcesJar${name.capitalize()}"(type: Jar) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java
@@ -429,17 +429,17 @@ public final class GeckoProfile {
         return new File(f, aFile);
     }
 
     /**
      * Retrieves the Gecko client ID from the filesystem. If the client ID does not exist, we attempt to migrate and
      * persist it from FHR and, if that fails, we attempt to create a new one ourselves.
      *
      * This method assumes the client ID is located in a file at a hard-coded path within the profile. The format of
-     * this file is a JSONObject which at the bottom level contains a String -> String mapping containing the client ID.
+     * this file is a JSONObject which at the bottom level contains a String -&gt; String mapping containing the client ID.
      *
      * WARNING: the platform provides a JSM to retrieve the client ID [1] and this would be a
      * robust way to access it. However, we don't want to rely on Gecko running in order to get
      * the client ID so instead we access the file this module accesses directly. However, it's
      * possible the format of this file (and the access calls in the jsm) will change, leaving
      * this code to fail. There are tests in TestGeckoProfile to verify the file format but be
      * warned: THIS IS NOT FOOLPROOF.
      *
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
@@ -1158,91 +1158,91 @@ public class GeckoSession extends LayerS
                 subjectName = identityData.getString("subjectName");
                 issuerCommonName = identityData.getString("issuerCommonName");
                 issuerOrganization = identityData.getString("issuerOrganization");
             }
         }
 
         /**
         * A View has started loading content from the network.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session GeckoSession that initiated the callback.
         * @param url The resource being loaded.
         */
         void onPageStart(GeckoSession session, String url);
 
         /**
         * A View has finished loading content from the network.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session GeckoSession that initiated the callback.
         * @param success Whether the page loaded successfully or an error occurred.
         */
         void onPageStop(GeckoSession session, boolean success);
 
         /**
         * The security status has been updated.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session GeckoSession that initiated the callback.
         * @param securityInfo The new security information.
         */
         void onSecurityChange(GeckoSession session, SecurityInformation securityInfo);
     }
 
     public interface ContentListener {
         /**
         * A page title was discovered in the content or updated after the content
         * loaded.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session The GeckoSession that initiated the callback.
         * @param title The title sent from the content.
         */
         void onTitleChange(GeckoSession session, String title);
 
         /**
          * A page has entered or exited full screen mode. Typically, the implementation
          * would set the Activity containing the GeckoSession to full screen when the page is
          * in full screen mode.
          *
-         * @param view The GeckoSession that initiated the callback.
+         * @param session The GeckoSession that initiated the callback.
          * @param fullScreen True if the page is in full screen mode.
          */
         void onFullScreen(GeckoSession session, boolean fullScreen);
 
 
         /**
          * A user has initiated the context menu via long-press.
          * This event is fired on links, (nested) images and (nested) media
          * elements.
          *
-         * @param view The GeckoSession that initiated the callback.
+         * @param session The GeckoSession that initiated the callback.
          * @param screenX The screen coordinates of the press.
          * @param screenY The screen coordinates of the press.
          * @param uri The URI of the pressed link, set for links and
          *            image-links.
          * @param elementSrc The source URI of the pressed element, set for
          *                   (nested) images and media elements.
          */
         void onContextMenu(GeckoSession session, int screenX, int screenY,
                            String uri, String elementSrc);
     }
 
     public interface NavigationListener {
         /**
         * A view has started loading content from the network.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session The GeckoSession that initiated the callback.
         * @param url The resource being loaded.
         */
         void onLocationChange(GeckoSession session, String url);
 
         /**
         * The view's ability to go back has changed.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session The GeckoSession that initiated the callback.
         * @param canGoBack The new value for the ability.
         */
         void onCanGoBack(GeckoSession session, boolean canGoBack);
 
         /**
         * The view's ability to go forward has changed.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session The GeckoSession that initiated the callback.
         * @param canGoForward The new value for the ability.
         */
         void onCanGoForward(GeckoSession session, boolean canGoForward);
 
         enum TargetWindow {
             DEFAULT(0),
             CURRENT(1),
             NEW(2);
@@ -1283,17 +1283,17 @@ public class GeckoSession extends LayerS
 
             private LoadUriResult(int value) {
                 mValue = value;
             }
         }
 
         /**
         * A request to open an URI.
-        * @param view The GeckoSession that initiated the callback.
+        * @param session The GeckoSession that initiated the callback.
         * @param uri The URI to be loaded.
         * @param where The target window.
         *
         * @return True if the URI loading has been handled, false if Gecko
         *         should handle the loading.
         */
         boolean onLoadUri(GeckoSession session, String uri, TargetWindow where);
     }
@@ -1317,66 +1317,74 @@ public class GeckoSession extends LayerS
             void dismiss();
 
             /**
              * Return whether the prompt shown should include a checkbox. For example, if
              * a page shows multiple prompts within a short period of time, the next
              * prompt will include a checkbox to let the user disable future prompts.
              * Although the API allows checkboxes for all prompts, in practice, only
              * alert/button/text/auth prompts will possibly have a checkbox.
+             *
+             * @return True if prompt includes a checkbox.
              */
             boolean hasCheckbox();
 
             /**
              * Return the message label for the optional checkbox.
+             *
+             * @return Checkbox message or null if none.
              */
             String getCheckboxMessage();
 
             /**
              * Return the initial value for the optional checkbox.
+             *
+             * @return Initial checkbox value.
              */
             boolean getCheckboxValue();
 
             /**
              * Set the current value for the optional checkbox.
+             *
+             * @param value New checkbox value.
              */
             void setCheckboxValue(boolean value);
         }
 
         /**
          * Display a simple message prompt.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param callback Callback interface.
          */
         void alert(GeckoSession session, String title, String msg, AlertCallback callback);
 
         /**
          * Callback interface for notifying the result of a button prompt.
          */
         interface ButtonCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when the button prompt is dismissed by
              * the user pressing one of the buttons.
+             *
+             * @param button Button result; one of BUTTON_TYPE_* constants.
              */
             void confirm(int button);
         }
 
         static final int BUTTON_TYPE_POSITIVE = 0;
         static final int BUTTON_TYPE_NEUTRAL = 1;
         static final int BUTTON_TYPE_NEGATIVE = 2;
 
         /**
          * Display a prompt with up to three buttons.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param btnMsg Array of 3 elements indicating labels for the individual buttons.
          *               btnMsg[BUTTON_TYPE_POSITIVE] is the label for the "positive" button.
          *               btnMsg[BUTTON_TYPE_NEUTRAL] is the label for the "neutral" button.
          *               btnMsg[BUTTON_TYPE_NEGATIVE] is the label for the "negative" button.
          *               The button is hidden if the corresponding label is null.
          * @param callback Callback interface.
@@ -1387,46 +1395,52 @@ public class GeckoSession extends LayerS
         /**
          * Callback interface for notifying the result of prompts that have text results,
          * including color and date/time pickers.
          */
         interface TextCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when the text prompt is confirmed by
              * the user, for example by pressing the "OK" button.
+             *
+             * @param text Text result.
              */
             void confirm(String text);
         }
 
         /**
          * Display a prompt for inputting text.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param value Default input text for the prompt.
          * @param callback Callback interface.
          */
         void promptForText(GeckoSession session, String title, String msg,
                            String value, TextCallback callback);
 
         /**
          * Callback interface for notifying the result of authentication prompts.
          */
         interface AuthCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when a password-only prompt is
              * confirmed by the user.
+             *
+             * @param password Entered password.
              */
             void confirm(String password);
 
             /**
              * Called by the prompt implementation when a username/password prompt is
              * confirmed by the user.
+             *
+             * @param username Entered username.
+             * @param password Entered password.
              */
             void confirm(String username, String password);
         }
 
         /**
          * The auth prompt is for a network host.
          */
         static final int AUTH_FLAG_HOST = 1;
@@ -1458,18 +1472,17 @@ public class GeckoSession extends LayerS
         /**
          * The auth request encrypts both password and data.
          */
         static final int AUTH_LEVEL_SECURE = 2;
 
         /**
          * Display a prompt for authentication credentials.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param options Bundle containing options for the prompt with keys,
          *                "flags": int, bit field of AUTH_FLAG_* flags;
          *                "uri": String, URI for the auth request or null if unknown;
          *                "level": int, one of AUTH_LEVEL_* indicating level of encryption;
          *                "username": String, initial username or null if password-only;
          *                "password": String, intiial password;
@@ -1530,18 +1543,17 @@ public class GeckoSession extends LayerS
         /**
          * Display choices in a list that allows multiple selections.
          */
         static final int CHOICE_TYPE_MULTIPLE = 3;
 
         /**
          * Display a menu prompt or list prompt.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog, or null for no title.
          * @param msg Message for the prompt dialog, or null for no message.
          * @param type One of CHOICE_TYPE_* indicating the type of prompt.
          * @param choices Array of bundles each representing an item or group, with keys,
          *                "disabled": boolean, true if the item should not be selectable;
          *                "icon": String, URI of the item icon or null if none
          *                        (only valid for menus);
          *                "id": String, ID of the item or group;
@@ -1555,18 +1567,17 @@ public class GeckoSession extends LayerS
          * @param callback Callback interface.
          */
         void promptForChoice(GeckoSession session, String title, String msg, int type,
                              GeckoBundle[] choices, ChoiceCallback callback);
 
         /**
          * Display a color prompt.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param value Initial color value in HTML color format.
          * @param callback Callback interface; the result passed to confirm() must be in
          *                 HTML color format.
          */
         void promptForColor(GeckoSession session, String title, String value,
                             TextCallback callback);
 
@@ -1593,18 +1604,17 @@ public class GeckoSession extends LayerS
         /**
          * Prompt for year, month, day, hour, and minute, without timezone.
          */
         static final int DATETIME_TYPE_DATETIME_LOCAL = 5;
 
         /**
          * Display a date/time prompt.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog; currently always null.
          * @param type One of DATETIME_TYPE_* indicating the type of prompt.
          * @param value Initial date/time value in HTML date/time format.
          * @param min Minimum date/time value in HTML date/time format.
          * @param max Maximum date/time value in HTML date/time format.
          * @param callback Callback interface; the result passed to confirm() must be in
          *                 HTML date/time format.
          */
@@ -1635,18 +1645,17 @@ public class GeckoSession extends LayerS
         }
 
         static final int FILE_TYPE_SINGLE = 1;
         static final int FILE_TYPE_MULTIPLE = 2;
 
         /**
          * Display a file prompt.
          *
-         * @param view The GeckoSession that triggered the prompt
-         *             or null if the prompt is a global prompt.
+         * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param type One of FILE_TYPE_* indicating the prompt type.
          * @param mimeTypes Array of permissible MIME types for the selected files, in
          *                  the form "type/subtype", where "type" and/or "subtype" can be
          *                  "*" to indicate any value.
          * @param callback Callback interface.
          */
         void promptForFile(GeckoSession session, String title, int type,
@@ -1656,17 +1665,17 @@ public class GeckoSession extends LayerS
     /**
      * GeckoSession applications implement this interface to handle content scroll
      * events.
      **/
     public interface ScrollListener {
         /**
          * The scroll position of the content has changed.
          *
-        * @param view The GeckoSession that initiated the callback.
+        * @param session GeckoSession that initiated the callback.
         * @param scrollX The new horizontal scroll position in pixels.
         * @param scrollY The new vertical scroll position in pixels.
         */
         public void onScrollChanged(GeckoSession session, int scrollX, int scrollY);
     }
 
     /**
      * GeckoSession applications implement this interface to handle requests for permissions
@@ -1693,30 +1702,30 @@ public class GeckoSession extends LayerS
              * implementation must call either grant() or reject() for every request.
              */
             void reject();
         }
 
         /**
          * Request Android app permissions.
          *
-         * @param view GeckoSession instance requesting the permissions.
+         * @param session GeckoSession instance requesting the permissions.
          * @param permissions List of permissions to request; possible values are,
          *                    android.Manifest.permission.ACCESS_FINE_LOCATION
          *                    android.Manifest.permission.CAMERA
          *                    android.Manifest.permission.RECORD_AUDIO
          * @param callback Callback interface.
          */
         void requestAndroidPermissions(GeckoSession session, String[] permissions,
                                        Callback callback);
 
         /**
          * Request content permission.
          *
-         * @param view GeckoSession instance requesting the permission.
+         * @param session GeckoSession instance requesting the permission.
          * @param uri The URI of the content requesting the permission.
          * @param type The type of the requested permission; possible values are,
          *             "geolocation": permission for using the geolocation API
          *             "desktop-notification": permission for using the notifications API
          * @param access Not used.
          * @param callback Callback interface.
          */
         void requestContentPermission(GeckoSession session, String uri, String type,
@@ -1757,17 +1766,17 @@ public class GeckoSession extends LayerS
              */
             void reject();
         }
 
         /**
          * Request content media permissions, including request for which video and/or
          * audio source to use.
          *
-         * @param view GeckoSession instance requesting the permission.
+         * @param session GeckoSession instance requesting the permission.
          * @param uri The URI of the content requesting the permission.
          * @param video List of video sources, or null if not requesting video.
          *              Each bundle represents a video source, with keys,
          *              "id": String, the origin-specific source identifier;
          *              "rawId": String, the non-origin-specific source identifier;
          *              "name": String, the name of the video source from the system
          *                      (for example, "Camera 0, Facing back, Orientation 90");
          *                      may be empty;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SysInfo.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SysInfo.java
@@ -35,22 +35,19 @@ public final class SysInfo {
     // these.
     private static volatile int cpuCount = -1;
 
     private static volatile int totalRAM = -1;
 
     /**
      * Get the number of cores on the device.
      *
-     * We can't use a nice tidy API call, because they're all wrong:
-     *
-     * <http://stackoverflow.com/questions/7962155/how-can-you-detect-a-dual-core-
-     * cpu-on-an-android-device-from-code>
-     *
-     * This method is based on that code.
+     * We can't use a nice tidy API call, <a
+     * href="https://stackoverflow.com/q/7962155">because they're all
+     * wrong</a>. This method is based on that code.
      *
      * @return the number of CPU cores, or 1 if the number could not be
      *         determined.
      */
     public static int getCPUCount() {
         if (cpuCount > 0) {
             return cpuCount;
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java
@@ -113,24 +113,23 @@ public final class TextInputController {
     }
 
     /**
      * Get a Handler for the background input method thread. In order to use a background
      * thread for input method operations on systems prior to Nougat, first override
      * {@code View.getHandler()} for the View returning the InputConnection instance, and
      * then call this method from the overridden method.
      *
-     * For example: <pre>{@code
-     * @Override
+     * For example:<pre>
+     * &#64;Override
      * public Handler getHandler() {
-     *     if (Build.VERSION.SDK_INT >= 24) {
+     *     if (Build.VERSION.SDK_INT &gt;= 24) {
      *         return super.getHandler();
      *     }
      *     return getSession().getTextInputController().getHandler(super.getHandler());
-     * }
      * }</pre>
      *
      * @param defHandler Handler returned by the system {@code getHandler} implementation.
      * @return Handler to return to the system through {@code getHandler}.
      */
     public @NonNull Handler getHandler(final @NonNull Handler defHandler) {
         // May be called on any thread.
         if (mInputConnection != null) {
@@ -180,17 +179,17 @@ public final class TextInputController {
         }
         mEditable.setListener((EditableListener) mInputConnection);
     }
 
     /**
      * Get an InputConnection instance. For full functionality, call {@link
      * #setView(View)} first before calling this method.
      *
-     * @param outAttrs EditorInfo instance to be filled on return.
+     * @param attrs EditorInfo instance to be filled on return.
      * @return InputConnection instance or null if input method is not active.
      */
     public @Nullable InputConnection onCreateInputConnection(final @NonNull EditorInfo attrs) {
         // May be called on any thread.
         if (!ensureInputConnection()) {
             return null;
         }
         return mInputConnection.onCreateInputConnection(attrs);
@@ -255,16 +254,17 @@ public final class TextInputController {
         }
         return mInputConnection.onKeyLongPress(keyCode, event);
     }
 
     /**
      * Process a KeyEvent as a multiple-press event.
      *
      * @param keyCode Key code.
+     * @param repeatCount Key repeat count.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyMultiple(final int keyCode, final int repeatCount,
                                  final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
         if (!ensureInputConnection()) {
             return false;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -77,17 +77,19 @@ public final class DynamicToolbarAnimato
 
         if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
             return mMaxToolbarHeight;
         }
         return 0;
     }
 
     /**
-     * If true, scroll changes will not affect translation.
+     * Return whether the toolbar is pinned and cannot be hidden.
+     *
+     * @return True if the toolbar is pinned.
      */
     public boolean isPinned() {
         ThreadUtils.assertOnUiThread();
 
         return !mPinFlags.isEmpty();
     }
 
     public boolean isPinnedBy(PinReason reason) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
@@ -314,17 +314,17 @@ public class LayerSession {
 
     /**
      * Get the bounds of the client area in client coordinates. The returned top-left
      * coordinates are always (0, 0). Use the matrix from {@link
      * #getClientToSurfaceMatrix(Matrix)} or {@link #getClientToScreenMatrix(Matrix)} to
      * map these bounds to surface or screen coordinates, respectively.
      *
      * @param rect RectF to be replaced by the client bounds in client coordinates.
-     * @see getSurfaceBounds(Rect)
+     * @see #getSurfaceBounds(Rect)
      */
     public void getClientBounds(@NonNull final RectF rect) {
         ThreadUtils.assertOnUiThread();
 
         rect.set(0.0f, 0.0f, (float) mWidth / mViewportZoom,
                              (float) mClientHeight / mViewportZoom);
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -144,17 +144,17 @@ public final class NativePanZoomControll
     /* package */ NativePanZoomController(final LayerSession session) {
         mSession = session;
     }
 
     /**
      * Set the current scroll factor. The scroll factor is the maximum scroll amount that
      * one scroll event may generate, in device pixels.
      *
-     * @return Scroll factor.
+     * @param factor Scroll factor.
      */
     public void setScrollFactor(final float factor) {
         ThreadUtils.assertOnUiThread();
         mPointerScrollFactor = factor;
     }
 
     /**
      * Get the current scroll factor.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContextUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContextUtils.java
@@ -13,18 +13,16 @@ import android.text.TextUtils;
 
 public class ContextUtils {
     private static final String INSTALLER_GOOGLE_PLAY = "com.android.vending";
 
     private ContextUtils() {}
 
     /**
      * @return {@link android.content.pm.PackageInfo#firstInstallTime} for the context's package.
-     * @throws PackageManager.NameNotFoundException Unexpected - we get the package name from the context so
-     *         it's expected to be found.
      */
     public static PackageInfo getCurrentPackageInfo(final Context context) {
         try {
             return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
         } catch (PackageManager.NameNotFoundException e) {
             throw new AssertionError("Should not happen: Can't get package info of own package");
         }
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/DateUtil.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/DateUtil.java
@@ -40,16 +40,16 @@ public class DateUtil {
         return getTimezoneOffsetInMinutesForGivenDate(Calendar.getInstance(timezone));
     }
 
     /**
      * Returns the time zone offset for the given date in minutes. The date makes a difference due to daylight
      * savings time in some regions. We return minutes because we can accurately represent time zones that are
      * offset by non-integer hour values, e.g. parts of New Zealand at UTC+12:45.
      *
-     * @param calendar A calendar with the appropriate time zone & date already set.
+     * @param calendar A calendar with the appropriate time zone &amp; date already set.
      */
     public static int getTimezoneOffsetInMinutesForGivenDate(@NonNull final Calendar calendar) {
         // via Date.getTimezoneOffset deprecated docs (note: it had incorrect order of operations).
         // Also, we cast to int because we should never overflow here - the max should be GMT+14 = 840.
         return (int) TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET));
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
@@ -136,17 +136,17 @@ public class FileUtils {
     /**
      * A generic solution to read from an input stream in UTF-8. This function will read from the stream until it
      * is finished and close the stream - this is necessary to close the wrapping resources.
      *
      * For a higher-level method, see {@link #readStringFromFile(File)}.
      *
      * Since this is generic, it may not be the most performant for your use case.
      *
-     * @param bufferSize Size of the underlying buffer for read optimizations - must be > 0.
+     * @param bufferSize Size of the underlying buffer for read optimizations - must be &gt; 0.
      */
     public static String readStringFromInputStreamAndCloseStream(final InputStream inputStream, final int bufferSize)
             throws IOException {
         InputStreamReader reader = null;
         try {
             if (bufferSize <= 0) {
                 throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize);
             }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
@@ -26,22 +26,19 @@ public class IntentUtils {
     public static final String ENV_VAR_IN_AUTOMATION = "MOZ_IN_AUTOMATION";
 
     private static final String ENV_VAR_REGEX = "(.+)=(.*)";
 
     private IntentUtils() {}
 
     /**
      * Returns a list of environment variables and their values. These are parsed from an Intent extra
-     * with the key -> value format:
-     *   env# -> ENV_VAR=VALUE
+     * with the key -&gt; value format: env# -&gt; ENV_VAR=VALUE, where # is an integer starting at 0.
      *
-     * # in env# is expected to be increasing from 0.
-     *
-     * @return A Map of environment variable name to value, e.g. ENV_VAR -> VALUE
+     * @return A Map of environment variable name to value, e.g. ENV_VAR -&gt; VALUE
      */
     public static HashMap<String, String> getEnvVarMap(@NonNull final SafeIntent intent) {
         // Optimization: get matcher for re-use. Pattern.matcher creates a new object every time so it'd be great
         // to avoid the unnecessary allocation, particularly because we expect to be called on the startup path.
         final Pattern envVarPattern = Pattern.compile(ENV_VAR_REGEX);
         final Matcher matcher = envVarPattern.matcher(""); // argument does not matter here.
 
         // This is expected to be an external intent so we should use SafeIntent to prevent crashing.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
@@ -226,17 +226,17 @@ public class StringUtils {
         return url;
     }
 
     public static String encodeUserEnteredUrl(String url) {
         return Uri.fromParts("user-entered", url, null).toString();
     }
 
     /**
-     * Compatibility layer for API < 11.
+     * Compatibility layer for API &lt; 11.
      *
      * Returns a set of the unique names of all query parameters. Iterating
      * over the set will return the names in order of their first occurrence.
      *
      * @param uri
      * @throws UnsupportedOperationException if this isn't a hierarchical URI
      *
      * @return a set of decoded names
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffix.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffix.java
@@ -26,18 +26,18 @@ import java.util.Set;
  * Some parts of the implementation of this class are based on InternetDomainName class of the Guava
  * project: https://github.com/google/guava
  */
 public class PublicSuffix {
     /**
      * Strip the public suffix from the domain. Returns the original domain if no public suffix
      * could be found.
      *
-     * www.mozilla.org -> www.mozilla
-     * independent.co.uk -> independent
+     * www.mozilla.org -&gt; www.mozilla
+     * independent.co.uk -&gt; independent
      */
     @NonNull
     @WorkerThread // This method might need to load data from disk
     public static String stripPublicSuffix(Context context, @NonNull String domain) {
         if (domain.length() == 0) {
             return domain;
         }
 
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -301,16 +301,107 @@ class MachCommands(MachCommandBase):
         """Create GeckoView archives.
         See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""")
     @CommandArgument('args', nargs=argparse.REMAINDER)
     def android_archive_geckoview(self, args):
         ret = self.gradle(self.substs['GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS'] + ["--continue"] + args, verbose=True)
 
         return ret
 
+    @SubCommand('android', 'geckoview-docs',
+        """Create GeckoView javadoc and optionally upload to Github""")
+    @CommandArgument('--archive', action='store_true',
+        help='Generate a javadoc archive.')
+    @CommandArgument('--upload', metavar='USER/REPO',
+        help='Upload generated javadoc to Github, '
+             'using the specified USER/REPO.')
+    @CommandArgument('--upload-branch', metavar='BRANCH[/PATH]',
+        default='gh-pages/javadoc',
+        help='Use the specified branch/path for commits.')
+    @CommandArgument('--upload-message', metavar='MSG',
+        default='GeckoView docs upload',
+        help='Use the specified message for commits.')
+    @CommandArgument('--variant', default='debug',
+        help='Gradle variant used to generate javadoc.')
+    def android_geckoview_docs(self, archive, upload, upload_branch,
+                               upload_message, variant):
+
+        def capitalize(s):
+            # Can't use str.capitalize because it lower cases trailing letters.
+            return (s[0].upper() + s[1:]) if s else ''
+
+        task = 'geckoview:javadoc' + ('Jar' if archive or upload else '') + capitalize(variant)
+        ret = self.gradle([task], verbose=True)
+        if ret or not upload:
+            return ret
+
+        # Upload to Github.
+        fmt = {
+            'level': os.environ.get('MOZ_SCM_LEVEL', '0'),
+            'project': os.environ.get('MH_BRANCH', 'unknown'),
+            'revision': os.environ.get('GECKO_HEAD_REV', 'tip'),
+        }
+        env = {}
+
+        # In order to push to GitHub from TaskCluster, we store a private key
+        # in the TaskCluster secrets store in the format {"content": "<KEY>"},
+        # and the corresponding public key as a writable deploy key for the
+        # destination repo on GitHub.
+        secret = os.environ.get('GECKOVIEW_DOCS_UPLOAD_SECRET', '').format(**fmt)
+        if secret:
+            # Set up a private key from the secrets store if applicable.
+            import requests
+            req = requests.get('http://taskcluster/secrets/v1/secret/' + secret)
+            req.raise_for_status()
+
+            keyfile = mozpath.abspath('gv-docs-upload-key')
+            with open(keyfile, 'w') as f:
+                os.chmod(keyfile, 0o600)
+                f.write(req.json()['secret']['content'])
+
+            # Turn off strict host key checking so ssh does not complain about
+            # unknown github.com host. We're not pushing anything sensitive, so
+            # it's okay to not check GitHub's host keys.
+            env['GIT_SSH_COMMAND'] = 'ssh -i "%s" -o StrictHostKeyChecking=no' % keyfile
+
+        # Clone remote repo.
+        branch, _, branch_path = upload_branch.partition('/')
+        repo_url = 'git@github.com:%s.git' % upload
+        repo_path = mozpath.abspath('gv-docs-repo')
+        self.run_process(['git', 'clone', '--branch', branch, '--depth', '1',
+                          repo_url, repo_path], append_env=env, pass_thru=True)
+        env['GIT_DIR'] = mozpath.join(repo_path, '.git')
+        env['GIT_WORK_TREE'] = repo_path
+        env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = 'GeckoView Docs Bot'
+        env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = 'nobody@mozilla.com'
+
+        # Extract new javadoc to specified directory inside repo.
+        import mozfile
+        src_tar = mozpath.join(self.topobjdir, 'gradle', 'build', 'mobile', 'android',
+                               'geckoview', 'libs', 'geckoview-javadoc.jar')
+        dst_path = mozpath.join(repo_path, branch_path.format(**fmt))
+        mozfile.remove(dst_path)
+        mozfile.extract_zip(src_tar, dst_path)
+
+        # Commit and push.
+        self.run_process(['git', 'add', '--all'], append_env=env, pass_thru=True)
+        if self.run_process(['git', 'diff', '--cached', '--quiet'],
+                            append_env=env, pass_thru=True, ensure_exit_code=False) != 0:
+            # We have something to commit.
+            self.run_process(['git', 'commit',
+                              '--message', upload_message.format(**fmt)],
+                             append_env=env, pass_thru=True)
+            self.run_process(['git', 'push', 'origin', 'gh-pages'],
+                             append_env=env, pass_thru=True)
+
+        mozfile.remove(repo_path)
+        if secret:
+            mozfile.remove(keyfile)
+        return 0
+
 
     @Command('gradle', category='devenv',
         description='Run gradle.',
         conditions=[conditions.is_android])
     @CommandArgument('-v', '--verbose', action='store_true',
         help='Verbose output for what commands the build is running.')
     @CommandArgument('args', nargs=argparse.REMAINDER)
     def gradle(self, args, verbose=False):
--- a/old-configure.in
+++ b/old-configure.in
@@ -1723,27 +1723,16 @@ AC_LANG_C
 dnl ========================================================
 dnl =  Internationalization checks
 dnl ========================================================
 dnl
 dnl Internationalization and Locale support is different
 dnl on various UNIX platforms.  Checks for specific i18n
 dnl features go here.
 
-dnl check for LC_MESSAGES
-AC_CACHE_CHECK(for LC_MESSAGES,
-               ac_cv_i18n_lc_messages,
-               [AC_TRY_COMPILE([#include <locale.h>],
-                               [int category = LC_MESSAGES;],
-                               ac_cv_i18n_lc_messages=yes,
-                               ac_cv_i18n_lc_messages=no)])
-if test "$ac_cv_i18n_lc_messages" = yes; then
-   AC_DEFINE(HAVE_I18N_LC_MESSAGES)
-fi
-
 AC_HAVE_FUNCS(localeconv)
 
 fi # ! SKIP_COMPILER_CHECKS
 
 if test -n "${COMPILE_ENVIRONMENT}"; then
   MOZ_CHECK_ALLOCATOR
 fi
 
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -3557,17 +3557,17 @@ dependencies = [
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.56.1"
-source = "git+https://github.com/servo/webrender#722e8b104bf99d29d61ecb1fe456eebe89ad81fd"
+source = "git+https://github.com/servo/webrender#2acfa55f11f51427a6b62b3fc93252365288d1a5"
 dependencies = [
  "app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3585,17 +3585,17 @@ dependencies = [
  "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.56.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.56.1"
-source = "git+https://github.com/servo/webrender#722e8b104bf99d29d61ecb1fe456eebe89ad81fd"
+source = "git+https://github.com/servo/webrender#2acfa55f11f51427a6b62b3fc93252365288d1a5"
 dependencies = [
  "app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -4604,17 +4604,18 @@ fn static_assert() {
     }
 
     pub fn reset_image_orientation(&mut self, other: &Self) {
         self.copy_image_orientation_from(other)
     }
 
     pub fn clone_image_orientation(&self) -> longhands::image_orientation::computed_value::T {
         use gecko_bindings::structs::nsStyleImageOrientation_Angles;
-        use properties::longhands::image_orientation::computed_value::{Orientation, T};
+        use properties::longhands::image_orientation::computed_value::T;
+        use values::computed::Orientation;
 
         let gecko_orientation = self.gecko.mImageOrientation.mOrientation;
         if gecko_orientation & structs::nsStyleImageOrientation_Bits_FROM_IMAGE_MASK as u8 != 0 {
             T::FromImage
         } else {
             const ANGLE0: u8 = nsStyleImageOrientation_Angles::ANGLE_0 as u8;
             const ANGLE90: u8 = nsStyleImageOrientation_Angles::ANGLE_90 as u8;
             const ANGLE180: u8 = nsStyleImageOrientation_Angles::ANGLE_180 as u8;
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -52,196 +52,16 @@
 ${helpers.single_keyword("image-rendering",
                          "auto",
                          extra_gecko_values="optimizespeed optimizequality -moz-crisp-edges",
                          extra_servo_values="pixelated crisp-edges",
                          custom_consts=image_rendering_custom_consts,
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-images/#propdef-image-rendering")}
 
-// Image Orientation
-<%helpers:longhand name="image-orientation"
-                   products="gecko"
-                   animation_value_type="discrete"
-                   gecko_pref="layout.css.image-orientation.enabled"
-    spec="https://drafts.csswg.org/css-images/#propdef-image-orientation, \
-      /// additional values in https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::Angle;
-
-
-    use std::f64::consts::PI;
-    const TWO_PI: f64 = 2.0 * PI;
-
-    #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
-    pub struct SpecifiedValue {
-        pub angle: Option<Angle>,
-        pub flipped: bool
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            if let Some(angle) = self.angle {
-                angle.to_css(dest)?;
-                if self.flipped {
-                    dest.write_str(" flip")
-                } else {
-                    Ok(())
-                }
-            } else {
-                if self.flipped {
-                    dest.write_str("flip")
-                } else {
-                    dest.write_str("from-image")
-                }
-            }
-        }
-    }
-
-    pub mod computed_value {
-        use std::fmt;
-        use style_traits::ToCss;
-        use values::specified::Angle;
-
-        #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
-        pub enum Orientation {
-            Angle0 = 0,
-            Angle90,
-            Angle180,
-            Angle270,
-        }
-
-        impl Orientation {
-            pub fn angle(&self) -> Angle {
-                match *self {
-                    Orientation::Angle0 => Angle::from_degrees(0.0, false),
-                    Orientation::Angle90 => Angle::from_degrees(90.0, false),
-                    Orientation::Angle180 => Angle::from_degrees(180.0, false),
-                    Orientation::Angle270 => Angle::from_degrees(270.0, false),
-                }
-            }
-        }
-
-        impl ToCss for Orientation {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                // Should agree with Angle::to_css.
-                match *self {
-                    Orientation::Angle0 => dest.write_str("0deg"),
-                    Orientation::Angle90 => dest.write_str("90deg"),
-                    Orientation::Angle180 => dest.write_str("180deg"),
-                    Orientation::Angle270 => dest.write_str("270deg"),
-                }
-            }
-        }
-
-        #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
-        pub enum T {
-            FromImage,
-            AngleWithFlipped(Orientation, bool),
-        }
-    }
-
-    use self::computed_value::Orientation;
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::AngleWithFlipped(Orientation::Angle0, false)
-    }
-
-    // According to CSS Content Module Level 3:
-    // The computed value of the property is calculated by rounding the specified angle
-    // to the nearest quarter-turn, rounding away from 0, then moduloing the value by 1 turn.
-    // This mirrors the Gecko implementation in
-    // nsStyleImageOrientation::CreateAsAngleAndFlip.
-    #[inline]
-    fn orientation_of_angle(angle: &computed::Angle) -> Orientation {
-        // Note that `angle` can be negative.
-        let mut rounded_angle = angle.radians64() % TWO_PI;
-        if rounded_angle < 0.0 {
-            // This computation introduces rounding error. Gecko previously
-            // didn't handle the negative case correctly; by branching we can
-            // match Gecko's behavior when it was correct.
-            rounded_angle = rounded_angle + TWO_PI;
-        }
-        if rounded_angle < 0.25 * PI {
-            return Orientation::Angle0
-        }
-        if rounded_angle < 0.75 * PI {
-            return Orientation::Angle90
-        }
-        if rounded_angle < 1.25 * PI {
-            return Orientation::Angle180
-        }
-        if rounded_angle < 1.75 * PI {
-            return Orientation::Angle270
-        }
-        Orientation::Angle0
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            if let Some(ref angle) = self.angle {
-                let angle = angle.to_computed_value(context);
-                let orientation = orientation_of_angle(&angle);
-                computed_value::T::AngleWithFlipped(orientation, self.flipped)
-            } else {
-                if self.flipped {
-                    computed_value::T::AngleWithFlipped(Orientation::Angle0, true)
-                } else {
-                    computed_value::T::FromImage
-                }
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            match *computed {
-                computed_value::T::FromImage => SpecifiedValue { angle: None, flipped: false },
-                computed_value::T::AngleWithFlipped(ref orientation, flipped) => {
-                    SpecifiedValue {
-                        angle: Some(orientation.angle()),
-                        flipped: flipped,
-                    }
-                }
-            }
-        }
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                computed_value::T::FromImage => dest.write_str("from-image"),
-                computed_value::T::AngleWithFlipped(angle, flipped) => {
-                    angle.to_css(dest)?;
-                    if flipped {
-                        dest.write_str(" flip")?;
-                    }
-                    Ok(())
-                },
-            }
-        }
-    }
-
-    // from-image | <angle> | [<angle>? flip]
-    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                         -> Result<SpecifiedValue, ParseError<'i>> {
-        if input.try(|input| input.expect_ident_matching("from-image")).is_ok() {
-            // Handle from-image
-            Ok(SpecifiedValue { angle: None, flipped: false })
-        } else if input.try(|input| input.expect_ident_matching("flip")).is_ok() {
-            // Handle flip
-            Ok(SpecifiedValue { angle: Some(Angle::zero()), flipped: true })
-        } else {
-            // Handle <angle> | <angle> flip
-            let angle = input.try(|input| Angle::parse(context, input)).ok();
-            if angle.is_none() {
-                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
-            }
-
-            let flipped = input.try(|input| input.expect_ident_matching("flip")).is_ok();
-            Ok(SpecifiedValue { angle: angle, flipped: flipped })
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("image-orientation",
+                          "ImageOrientation",
+                          "computed::ImageOrientation::zero()",
+                          products="gecko",
+                          animation_value_type="discrete",
+                          gecko_pref="layout.css.image-orientation.enabled",
+                          spec="https://drafts.csswg.org/css-images/#propdef-image-orientation, \
+                    /// additional values in https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation")}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/inherited_box.rs
@@ -0,0 +1,75 @@
+/* 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/. */
+
+//! Computed values for inherited box
+
+use std::fmt;
+use style_traits::ToCss;
+use values::specified::Angle;
+
+/// An angle rounded and normalized per https://drafts.csswg.org/css-images/#propdef-image-orientation
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
+pub enum Orientation {
+    Angle0 = 0,
+    Angle90,
+    Angle180,
+    Angle270,
+}
+
+impl Orientation {
+    /// Get the actual angle that this orientation value represents.
+    pub fn angle(&self) -> Angle {
+        match *self {
+            Orientation::Angle0 => Angle::from_degrees(0.0, false),
+            Orientation::Angle90 => Angle::from_degrees(90.0, false),
+            Orientation::Angle180 => Angle::from_degrees(180.0, false),
+            Orientation::Angle270 => Angle::from_degrees(270.0, false),
+        }
+    }
+}
+
+impl ToCss for Orientation {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        // Should agree with Angle::to_css.
+        match *self {
+            Orientation::Angle0 => dest.write_str("0deg"),
+            Orientation::Angle90 => dest.write_str("90deg"),
+            Orientation::Angle180 => dest.write_str("180deg"),
+            Orientation::Angle270 => dest.write_str("270deg"),
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-images/#propdef-image-orientation
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
+pub enum ImageOrientation {
+    /// 'from-image'
+    FromImage,
+
+    /// '<angle>' | '<angle>? flip'
+    AngleWithFlipped(Orientation, bool),
+}
+
+impl ImageOrientation {
+    #[allow(missing_docs)]
+    pub fn zero() -> Self {
+        ImageOrientation::AngleWithFlipped(Orientation::Angle0, false)
+    }
+}
+
+impl ToCss for ImageOrientation {
+    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
+        match *self {
+            ImageOrientation::FromImage => dest.write_str("from-image"),
+            ImageOrientation::AngleWithFlipped(angle, flipped) => {
+                angle.to_css(dest)?;
+                if flipped {
+                    dest.write_str(" flip")?;
+                }
+                Ok(())
+            },
+        }
+    }
+}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -41,16 +41,17 @@ pub use self::font::{FontFamily, FontLan
 pub use self::font::{FontVariantLigatures, FontVariantNumeric, FontFeatureSettings};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XTextZoom, XLang};
 pub use self::box_::{AnimationIterationCount, AnimationName, Display, OverscrollBehavior, Contain};
 pub use self::box_::{OverflowClipBox, ScrollSnapType, TouchAction, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
+pub use self::inherited_box::{Orientation, ImageOrientation};
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 pub use super::specified::{BorderStyle, TextDecorationLine};
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNone, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{CSSPixelLength, NonNegativeLength, NonNegativeLengthOrPercentage};
@@ -76,16 +77,17 @@ pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
 pub mod box_;
 pub mod color;
 pub mod effects;
 pub mod flex;
 pub mod font;
 pub mod image;
+pub mod inherited_box;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod length;
 pub mod list;
 pub mod outline;
 pub mod percentage;
 pub mod position;
 pub mod rect;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/inherited_box.rs
@@ -0,0 +1,139 @@
+/* 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/. */
+
+//! Specified values for inherited box
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::f64::consts::PI;
+use std::fmt;
+use style_traits::{ParseError, StyleParseErrorKind, ToCss};
+use values::computed;
+use values::computed::{Context, Orientation, ToComputedValue};
+use values::specified::Angle;
+
+/// The specified value of the `image-orientation` property.
+/// https://drafts.csswg.org/css-images/#propdef-image-orientation
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
+pub struct ImageOrientation {
+    /// The angle specified, if any
+    pub angle: Option<Angle>,
+
+    /// Whether or not "flip" was specified
+    pub flipped: bool
+}
+
+impl ToCss for ImageOrientation {
+    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
+        if let Some(angle) = self.angle {
+            angle.to_css(dest)?;
+            if self.flipped {
+                dest.write_str(" flip")
+            } else {
+                Ok(())
+            }
+        } else {
+            if self.flipped {
+                dest.write_str("flip")
+            } else {
+                dest.write_str("from-image")
+            }
+        }
+    }
+}
+
+const TWO_PI: f64 = 2.0 * PI;
+
+// According to CSS Content Module Level 3:
+// The computed value of the property is calculated by rounding the specified angle
+// to the nearest quarter-turn, rounding away from 0, then moduloing the value by 1 turn.
+// This mirrors the Gecko implementation in
+// nsStyleImageOrientation::CreateAsAngleAndFlip.
+#[inline]
+fn orientation_of_angle(angle: &computed::Angle) -> Orientation {
+    // Note that `angle` can be negative.
+    let mut rounded_angle = angle.radians64() % TWO_PI;
+    if rounded_angle < 0.0 {
+        // This computation introduces rounding error. Gecko previously
+        // didn't handle the negative case correctly; by branching we can
+        // match Gecko's behavior when it was correct.
+        rounded_angle += TWO_PI;
+    }
+    if rounded_angle < 0.25 * PI {
+        return Orientation::Angle0
+    }
+    if rounded_angle < 0.75 * PI {
+        return Orientation::Angle90
+    }
+    if rounded_angle < 1.25 * PI {
+        return Orientation::Angle180
+    }
+    if rounded_angle < 1.75 * PI {
+        return Orientation::Angle270
+    }
+    Orientation::Angle0
+}
+
+impl ToComputedValue for ImageOrientation {
+    type ComputedValue = computed::ImageOrientation;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> computed::ImageOrientation {
+        if let Some(ref angle) = self.angle {
+            let angle = angle.to_computed_value(context);
+            let orientation = orientation_of_angle(&angle);
+            computed::ImageOrientation::AngleWithFlipped(orientation, self.flipped)
+        } else {
+            if self.flipped {
+                computed::ImageOrientation::zero()
+            } else {
+                computed::ImageOrientation::FromImage
+            }
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &computed::ImageOrientation) -> Self {
+        match *computed {
+            computed::ImageOrientation::FromImage => {
+                ImageOrientation {
+                    angle: None,
+                    flipped: false
+                }
+            },
+
+            computed::ImageOrientation::AngleWithFlipped(ref orientation, flipped) => {
+                ImageOrientation {
+                    angle: Some(orientation.angle()),
+                    flipped: flipped,
+                }
+            }
+        }
+    }
+}
+
+impl Parse for ImageOrientation {
+    // from-image | <angle> | [<angle>? flip]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        if input.try(|input| input.expect_ident_matching("from-image")).is_ok() {
+            // Handle from-image
+            Ok(ImageOrientation { angle: None, flipped: false })
+        } else if input.try(|input| input.expect_ident_matching("flip")).is_ok() {
+            // Handle flip
+            Ok(ImageOrientation { angle: Some(Angle::zero()), flipped: true })
+        } else {
+            // Handle <angle> | <angle> flip
+            let angle = input.try(|input| Angle::parse(context, input)).ok();
+            if angle.is_none() {
+                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+            }
+
+            let flipped = input.try(|input| input.expect_ident_matching("flip")).is_ok();
+            Ok(ImageOrientation { angle: angle, flipped: flipped })
+        }
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -38,16 +38,17 @@ pub use self::box_::{AnimationIterationC
 pub use self::box_::{OverflowClipBox, ScrollSnapType, TouchAction, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageLayer, MozImageRect};
+pub use self::inherited_box::ImageOrientation;
 pub use self::length::{AbsoluteLength, CalcLengthOrPercentage, CharacterWidth};
 pub use self::length::{FontRelativeLength, Length, LengthOrNone, LengthOrNumber};
 pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{NoCalcLength, ViewportPercentageLength};
 pub use self::length::NonNegativeLengthOrPercentage;
 pub use self::list::{ListStyleImage, Quotes};
 #[cfg(feature = "gecko")]
@@ -78,16 +79,17 @@ pub mod calc;
 pub mod color;
 pub mod effects;
 pub mod flex;
 pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
+pub mod inherited_box;
 pub mod length;
 pub mod list;
 pub mod outline;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod source_size_list;
 pub mod svg;
--- a/taskcluster/ci/artifact-build/kind.yml
+++ b/taskcluster/ci/artifact-build/kind.yml
@@ -22,17 +22,16 @@ jobs:
         treeherder:
             platform: linux64/opt
             kind: build
             symbol: AB
             tier: 2
         run-on-projects: ['trunk', 'try']
         worker-type: aws-provisioner-v1/gecko-{level}-b-linux
         worker:
-            docker-image: {in-tree: desktop-build}
             max-run-time: 36000
         run:
             using: mozharness
             actions: [get-secrets build]
             config:
                 - builds/releng_base_firefox.py
                 - builds/releng_sub_linux_configs/64_artifact.py
                 - balrog/production.py
--- a/taskcluster/ci/build/android-stuff.yml
+++ b/taskcluster/ci/build/android-stuff.yml
@@ -201,8 +201,52 @@ android-findbugs/opt:
     optimization:
         skip-unless-changed:
             - "mobile/android/**/*.java"
             - "mobile/android/**/Makefile.in"
             - "mobile/android/config/**"
             - "mobile/android/gradle.configure"
             - "mobile/android/**/moz.build"
             - "**/*.gradle"
+
+android-geckoview-docs/opt:
+    description: "Android GeckoView docs"
+    index:
+        product: mobile
+        job-name: android-geckoview-docs
+    treeherder:
+        platform: android-4-0-armv7-api16/opt
+        kind: build
+        tier: 3
+        symbol: tc-A(gv-docs)
+    run-on-projects: [mozilla-central]
+    worker-type: aws-provisioner-v1/gecko-{level}-b-android
+    worker:
+        docker-image: {in-tree: android-build}
+        env:
+            GRADLE_USER_HOME: "/builds/worker/workspace/build/src/mobile/android/gradle/dotgradle-offline"
+            PERFHERDER_EXTRA_OPTIONS: android-geckoview-docs
+            GECKOVIEW_DOCS_UPLOAD_SECRET: "project/releng/gecko/build/level-{level}/geckoview-docs-upload"
+        artifacts:
+            - name: public/android/geckoview-docs/geckoview-javadoc.jar
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/libs/geckoview-javadoc.jar
+              type: file
+        max-run-time: 36000
+    run:
+        using: mozharness
+        actions: [get-secrets build]
+        config:
+            - builds/releng_base_android_64_builds.py
+            - disable_signing.py
+            - platform_supports_post_upload_to_latest.py
+        script: "mozharness/scripts/fx_desktop_build.py"
+        secrets: true
+        custom-build-variant-cfg: android-geckoview-docs
+        tooltool-downloads: internal
+    toolchains:
+        - android-gradle-dependencies
+        - android-sdk-linux
+        - proguard-jar
+    optimization:
+        skip-unless-changed:
+            - "mobile/android/**/*.java"
+            - "mobile/android/gradle.configure"
+            - "**/*.gradle"
--- a/taskcluster/ci/build/macosx.yml
+++ b/taskcluster/ci/build/macosx.yml
@@ -141,17 +141,16 @@ macosx64-noopt/debug:
         product: firefox
         job-name: macosx64-noopt-debug
     treeherder:
         platform: osx-cross-noopt/debug
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
-        docker-image: {in-tree: desktop-build}
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_firefox.py
--- a/taskcluster/ci/l10n/kind.yml
+++ b/taskcluster/ci/l10n/kind.yml
@@ -36,21 +36,19 @@ job-template:
          macosx64.*: [ja]
          default: [ja-JP-mac]
    run-time:
       by-build-platform:
          default: 36000
          android-api-16-l10n: 18000
    docker-image:
       by-build-platform:
-         default:
-            in-tree: desktop-build
          android-api-16-l10n:
             in-tree: android-build
-         win.*: null
+         default: null
    secrets:
       by-build-platform:
          default: false
          android-api-16-l10n: true
    toolchains:
       by-build-platform:
          default: []
          macosx64:
--- a/taskcluster/ci/nightly-l10n/kind.yml
+++ b/taskcluster/ci/nightly-l10n/kind.yml
@@ -46,21 +46,19 @@ job-template:
          macosx64.*: [ja]
          default: [ja-JP-mac]
    run-time:
       by-build-platform:
          default: 36000
          android-api-16-nightly: 18000
    docker-image:
       by-build-platform:
-         default:
-            in-tree: desktop-build
          android-api-16-nightly:
             in-tree: android-build
-         win.*: null
+         default: null
    secrets:
       by-build-platform:
          default: false
          android-api-16-nightly: true
    toolchains:
       by-build-platform:
          default: []
          macosx64.*:
--- a/taskcluster/ci/searchfox/kind.yml
+++ b/taskcluster/ci/searchfox/kind.yml
@@ -26,17 +26,16 @@ jobs:
     linux64-searchfox/debug:
         description: "Linux64 Debug Searchfox"
         index:
             job-name: linux64-searchfox-debug
         treeherder:
             platform: linux64/debug
         worker-type: aws-provisioner-v1/gecko-{level}-b-linux
         worker:
-            docker-image: {in-tree: desktop-build}
             max-run-time: 36000
         run:
             using: mozharness
             actions: [clobber build]
             config:
                 - builds/releng_base_firefox.py
                 - builds/releng_sub_linux_configs/64_searchfox_and_debug.py
                 - balrog/production.py
@@ -50,17 +49,16 @@ jobs:
     macosx64-searchfox/debug:
         description: "MacOS X x64 Debug Cross-compile Searchfox"
         index:
             job-name: macosx64-searchfox-debug
         treeherder:
             platform: osx-cross/debug
         worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
         worker:
-            docker-image: {in-tree: desktop-build}
             max-run-time: 36000
             env:
                 TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
         run:
             using: mozharness
             actions: [clobber get-secrets build update]
             config:
                 - builds/releng_base_firefox.py
--- a/taskcluster/ci/spidermonkey/kind.yml
+++ b/taskcluster/ci/spidermonkey/kind.yml
@@ -59,281 +59,11 @@ job-defaults:
             .*-b-win2012:
                 - win64-clang-cl
                 - win64-rust
             default:
                 - linux64-clang
                 - linux64-gcc
                 - linux64-rust
 
-jobs:
-    sm-package-linux64/opt:
-        description: "Spidermonkey source package and test"
-        index:
-            job-name: sm-package-linux64-opt
-        treeherder:
-            symbol: SM-tc(pkg)
-            platform: linux64/opt
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            using: spidermonkey-package
-            spidermonkey-variant: plain
-
-    sm-mozjs-sys-linux64/debug:
-        description: "Build js/src as the mozjs_sys Rust crate"
-        index:
-            job-name: sm-mozjs-sys-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(mozjs-crate)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            using: spidermonkey-mozjs-crate
-            spidermonkey-variant: plain
-        run-on-projects: ['integration', 'release', 'try']
-
-    sm-rust-bindings-linux64/debug:
-        description: "Build and test the Rust bindings for SpiderMonkey"
-        index:
-            job-name: sm-rust-bindings-linux64-debug
-        treeherder:
-            symbol: SM-tc(rust)
-            tier: 2
-            platform: linux64/debug
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            using: spidermonkey-rust-bindings
-            spidermonkey-variant: plain
-        run-on-projects: ['integration', 'release', 'try']
-
-    sm-plain-linux64/debug:
-        description: "Spidermonkey Plain"
-        index:
-            job-name: sm-plain-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(p)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: plaindebug
-
-    sm-plain-win32/debug:
-        description: "Spidermonkey Plain"
-        index:
-            job-name: sm-plain-win32-debug
-        treeherder:
-            platform: windows2012-32/debug
-            symbol: SM-tc(p)
-        worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
-        worker:
-            max-run-time: 36000
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
-        run:
-            spidermonkey-variant: plaindebug
-
-    sm-plain-linux64/opt:
-        description: "Spidermonkey Plain"
-        index:
-            job-name: sm-plain-linux64-opt
-        treeherder:
-            symbol: SM-tc(p)
-            platform: linux64/opt
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: plain
-
-    sm-nojit-linux64/opt:
-        description: "Spidermonkey no JIT"
-        index:
-            job-name: sm-nojit-linux64-opt
-        treeherder:
-            symbol: SM-tc(nojit)
-            platform: linux64/opt
-        worker:
-            max-run-time: 3600
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: nojit
-
-    sm-plain-win32/opt:
-        description: "Spidermonkey Plain"
-        index:
-            job-name: sm-plain-win32-opt
-        treeherder:
-            platform: windows2012-32/opt
-            symbol: SM-tc(p)
-        worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
-        worker:
-            max-run-time: 36000
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
-        run:
-            spidermonkey-variant: plain
-
-    sm-arm-sim-linux32/debug:
-        description: "Spidermonkey ARM sim"
-        index:
-            job-name: sm-arm-sim-linux32-debug
-        treeherder:
-            platform: linux32/debug
-            symbol: SM-tc(arm)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-        run:
-            spidermonkey-variant: arm-sim
-
-    sm-arm64-sim-linux64/debug:
-        description: "Spidermonkey ARM64 sim"
-        index:
-            job-name: sm-arm64-sim-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(arm64)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: arm64-sim
-
-    sm-asan-linux64/opt:
-        description: "Spidermonkey Address Sanitizer"
-        index:
-            job-name: sm-asan-linux64-opt
-        treeherder:
-            symbol: SM-tc(asan)
-            platform: linux64/opt
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: asan
-
-    sm-compacting-linux64/debug:
-        description: "Spidermonkey Compacting"
-        index:
-            job-name: sm-compacting-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(cgc)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: compacting
-
-    sm-compacting-win32/debug:
-        description: "Spidermonkey Compacting"
-        index:
-            job-name: sm-compacting-win32-debug
-        treeherder:
-            platform: windows2012-32/debug
-            symbol: SM-tc(cgc)
-        worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
-        worker:
-            max-run-time: 36000
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
-        run:
-            spidermonkey-variant: compacting
-
-    sm-msan-linux64/opt:
-        description: "Spidermonkey Memory Sanitizer"
-        index:
-            job-name: sm-msan-linux64-opt
-        treeherder:
-            symbol: SM-tc(msan)
-            platform: linux64/opt
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: msan
-
-    sm-tsan-linux64/opt:
-        description: "Spidermonkey Thread Sanitizer"
-        index:
-            job-name: sm-tsan-linux64-opt
-        treeherder:
-            symbol: SM-tc(tsan)
-            tier: 3
-            platform: linux64/opt
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-        run:
-            spidermonkey-variant: tsan
-
-    sm-rootanalysis-linux64/debug:
-        description: "Spidermonkey Root Analysis"
-        index:
-            job-name: sm-rootanalysis-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(r)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: rootanalysis
-
-    sm-nonunified-linux64/debug:
-        description: "Spidermonkey Non-Unified Debug"
-        index:
-            job-name: sm-nonunified-linux64-debug
-        treeherder:
-            platform: linux64/debug
-            symbol: SM-tc(nu)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: nonunified
-
-    sm-fuzzing-linux64/opt:
-        description: "Spidermonkey Fuzzing"
-        index:
-            job-name: sm-fuzzing-linux64
-        treeherder:
-            platform: linux64/opt
-            symbol: SM-tc(f)
-        worker:
-            max-run-time: 36000
-            docker-image: {in-tree: desktop-build}
-            env:
-                TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
-        run:
-            spidermonkey-variant: fuzzing
+jobs-from:
+    - linux.yml
+    - windows.yml
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/spidermonkey/linux.yml
@@ -0,0 +1,167 @@
+# 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/.
+
+job-defaults:
+    worker:
+        max-run-time: 36000
+        docker-image: {in-tree: desktop-build}
+        env:
+            TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/jsshell.manifest"
+
+sm-package-linux64/opt:
+    description: "Spidermonkey source package and test"
+    index:
+        job-name: sm-package-linux64-opt
+    treeherder:
+        symbol: SM-tc(pkg)
+        platform: linux64/opt
+    run:
+        using: spidermonkey-package
+        spidermonkey-variant: plain
+
+sm-mozjs-sys-linux64/debug:
+    description: "Build js/src as the mozjs_sys Rust crate"
+    index:
+        job-name: sm-mozjs-sys-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(mozjs-crate)
+    run:
+        using: spidermonkey-mozjs-crate
+        spidermonkey-variant: plain
+    run-on-projects: ['integration', 'release', 'try']
+
+sm-rust-bindings-linux64/debug:
+    description: "Build and test the Rust bindings for SpiderMonkey"
+    index:
+        job-name: sm-rust-bindings-linux64-debug
+    treeherder:
+        symbol: SM-tc(rust)
+        tier: 2
+        platform: linux64/debug
+    run:
+        using: spidermonkey-rust-bindings
+        spidermonkey-variant: plain
+    run-on-projects: ['integration', 'release', 'try']
+
+sm-plain-linux64/debug:
+    description: "Spidermonkey Plain"
+    index:
+        job-name: sm-plain-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(p)
+    run:
+        spidermonkey-variant: plaindebug
+
+sm-plain-linux64/opt:
+    description: "Spidermonkey Plain"
+    index:
+        job-name: sm-plain-linux64-opt
+    treeherder:
+        symbol: SM-tc(p)
+        platform: linux64/opt
+    run:
+        spidermonkey-variant: plain
+
+sm-nojit-linux64/opt:
+    description: "Spidermonkey no JIT"
+    index:
+        job-name: sm-nojit-linux64-opt
+    treeherder:
+        symbol: SM-tc(nojit)
+        platform: linux64/opt
+    run:
+        spidermonkey-variant: nojit
+
+sm-arm-sim-linux32/debug:
+    description: "Spidermonkey ARM sim"
+    index:
+        job-name: sm-arm-sim-linux32-debug
+    treeherder:
+        platform: linux32/debug
+        symbol: SM-tc(arm)
+    run:
+        spidermonkey-variant: arm-sim
+
+sm-arm64-sim-linux64/debug:
+    description: "Spidermonkey ARM64 sim"
+    index:
+        job-name: sm-arm64-sim-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(arm64)
+    run:
+        spidermonkey-variant: arm64-sim
+
+sm-asan-linux64/opt:
+    description: "Spidermonkey Address Sanitizer"
+    index:
+        job-name: sm-asan-linux64-opt
+    treeherder:
+        symbol: SM-tc(asan)
+        platform: linux64/opt
+    run:
+        spidermonkey-variant: asan
+
+sm-compacting-linux64/debug:
+    description: "Spidermonkey Compacting"
+    index:
+        job-name: sm-compacting-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(cgc)
+    run:
+        spidermonkey-variant: compacting
+
+sm-msan-linux64/opt:
+    description: "Spidermonkey Memory Sanitizer"
+    index:
+        job-name: sm-msan-linux64-opt
+    treeherder:
+        symbol: SM-tc(msan)
+        platform: linux64/opt
+    run:
+        spidermonkey-variant: msan
+
+sm-tsan-linux64/opt:
+    description: "Spidermonkey Thread Sanitizer"
+    index:
+        job-name: sm-tsan-linux64-opt
+    treeherder:
+        symbol: SM-tc(tsan)
+        tier: 3
+        platform: linux64/opt
+    run:
+        spidermonkey-variant: tsan
+
+sm-rootanalysis-linux64/debug:
+    description: "Spidermonkey Root Analysis"
+    index:
+        job-name: sm-rootanalysis-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(r)
+    run:
+        spidermonkey-variant: rootanalysis
+
+sm-nonunified-linux64/debug:
+    description: "Spidermonkey Non-Unified Debug"
+    index:
+        job-name: sm-nonunified-linux64-debug
+    treeherder:
+        platform: linux64/debug
+        symbol: SM-tc(nu)
+    run:
+        spidermonkey-variant: nonunified
+
+sm-fuzzing-linux64/opt:
+    description: "Spidermonkey Fuzzing"
+    index:
+        job-name: sm-fuzzing-linux64
+    treeherder:
+        platform: linux64/opt
+        symbol: SM-tc(f)
+    run:
+        spidermonkey-variant: fuzzing
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/spidermonkey/windows.yml
@@ -0,0 +1,40 @@
+# 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/.
+
+job-defaults:
+    worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
+    worker:
+        max-run-time: 36000
+        env:
+            TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+
+sm-plain-win32/debug:
+    description: "Spidermonkey Plain"
+    index:
+        job-name: sm-plain-win32-debug
+    treeherder:
+        platform: windows2012-32/debug
+        symbol: SM-tc(p)
+    run:
+        spidermonkey-variant: plaindebug
+
+sm-plain-win32/opt:
+    description: "Spidermonkey Plain"
+    index:
+        job-name: sm-plain-win32-opt
+    treeherder:
+        platform: windows2012-32/opt
+        symbol: SM-tc(p)
+    run:
+        spidermonkey-variant: plain
+
+sm-compacting-win32/debug:
+    description: "Spidermonkey Compacting"
+    index:
+        job-name: sm-compacting-win32-debug
+    treeherder:
+        platform: windows2012-32/debug
+        symbol: SM-tc(cgc)
+    run:
+        spidermonkey-variant: compacting
--- a/taskcluster/ci/static-analysis/kind.yml
+++ b/taskcluster/ci/static-analysis/kind.yml
@@ -25,17 +25,16 @@ jobs:
     linux64-st-an/debug:
         description: "Linux64 Debug Static Analysis"
         index:
             job-name: linux64-st-an-debug
         treeherder:
             platform: linux64/debug
         worker-type: aws-provisioner-v1/gecko-{level}-b-linux
         worker:
-            docker-image: {in-tree: desktop-build}
             max-run-time: 36000
         run:
             using: mozharness
             actions: [build]
             config:
                 - builds/releng_base_firefox.py
                 - builds/releng_sub_linux_configs/64_stat_and_debug.py
                 - balrog/production.py
@@ -50,17 +49,16 @@ jobs:
     linux64-st-an/opt:
         description: "Linux64 Opt Static Analysis"
         index:
             job-name: linux64-st-an-opt
         treeherder:
             platform: linux64/opt
         worker-type: aws-provisioner-v1/gecko-{level}-b-linux
         worker:
-            docker-image: {in-tree: desktop-build}
             max-run-time: 36000
         run:
             using: mozharness
             actions: [build]
             config:
                 - builds/releng_base_firefox.py
                 - builds/releng_sub_linux_configs/64_stat_and_opt.py
                 - balrog/production.py
--- a/taskcluster/docker/android-build/Dockerfile
+++ b/taskcluster/docker/android-build/Dockerfile
@@ -27,37 +27,34 @@ ENV HOME=/builds/worker \
     DEBIAN_FRONTEND=noninteractive
 
 # Set a default command useful for debugging.
 CMD ["/bin/bash", "--login"]
 
 # Set apt sources list to a snapshot.
 COPY sources.list /etc/apt/
 
-# We need i386 packages for the Android SDK.
-# Once https://bugzilla.mozilla.org/show_bug.cgi?id=1370119 is in-tree, we
-# will have 64-bit builds of everything, and we can then remove this and
-# the :i386 packages we install below.
-RUN dpkg --add-architecture i386
-
 # rsync is required for l10n single locale repacks.  less, screen, and
 # vim, help debugging interactive tasks in Task Cluster.
+# git and openssh-client are used to upload GeckoView javadoc to Github.
 RUN apt-get update -q && \
     apt-get install -yyq --no-install-recommends \
       autoconf2.13 \
       build-essential \
       base-files \
       ca-certificates \
       ccache \
       cmake \
       curl \
       file \
+      git \
       gnupg \
       less \
       make \
+      openssh-client \
       procps \
       python \
       python-cryptography \
       python-dev \
       python-pip \
       python-setuptools \
       python-virtualenv \
       rsync \
@@ -66,20 +63,16 @@ RUN apt-get update -q && \
       tar \
       unzip \
       uuid \
       vim \
       wget \
       xz-utils \
       yasm \
       zip \
-      libstdc++6:i386 \
-      libgcc1:i386 \
-      zlib1g:i386 \
-      libncurses5:i386 \
     && \
     apt-get clean
 
 # %include python/mozbuild/mozbuild/action/tooltool.py
 COPY topsrcdir/python/mozbuild/mozbuild/action/tooltool.py /setup/tooltool.py
 
 # %include testing/mozharness/external_tools/robustcheckout.py
 COPY topsrcdir/testing/mozharness/external_tools/robustcheckout.py /usr/local/mercurial/robustcheckout.py
--- a/taskcluster/taskgraph/transforms/job/mozharness.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness.py
@@ -131,18 +131,17 @@ def mozharness_on_docker_worker_setup(co
                                   "'use-simple-package' on docker-workers")
     if not run['use-magic-mh-args']:
         raise NotImplementedError("Cannot disabled mh magic arg passing via"
                                   "'use-magic-mh-args' on docker-workers")
 
     # Running via mozharness assumes an image that contains build.sh:
     # by default, desktop-build, but it could be another image (like
     # android-build) that "inherits" from desktop-build.
-    if not taskdesc['worker']['docker-image']:
-        taskdesc['worker']['docker-image'] = {"in-tree": "desktop-build"}
+    taskdesc['worker'].setdefault('docker-image', {'in-tree': 'desktop-build'})
 
     worker['taskcluster-proxy'] = run.get('taskcluster-proxy')
 
     docker_worker_add_public_artifacts(config, job, taskdesc)
     docker_worker_add_workspace_cache(config, job, taskdesc,
                                       extra=run.get('extra-workspace-cache-key'))
     support_vcs_checkout(config, job, taskdesc)
 
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/builds/releng_sub_android_configs/64_geckoview_docs.py
@@ -0,0 +1,20 @@
+config = {
+    'base_name': 'Android GeckoView docs %(branch)s',
+    'stage_platform': 'android-geckoview-docs',
+    'build_type': 'api-16-opt',
+    'src_mozconfig': 'mobile/android/config/mozconfigs/android-api-16-frontend/nightly',
+    'multi_locale_config_platform': 'android',
+    # geckoview-docs doesn't produce a package. So don't collect package metrics.
+    'disable_package_metrics': True,
+    'postflight_build_mach_commands': [
+        ['android',
+         'geckoview-docs',
+         '--archive',
+         '--upload', 'mozilla/geckoview',
+         '--upload-branch', 'gh-pages/javadoc/{project}',
+         '--upload-message', 'Update {project} javadoc to rev {revision}',
+        ],
+    ],
+    'artifact_flag_build_variant_in_try': None, # There's no artifact equivalent.
+    'max_build_output_timeout': 0,
+}
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -440,16 +440,17 @@ class BuildOptionParser(object):
         'x86-old-id': 'builds/releng_sub_%s_configs/%s_x86_old_id.py',
         'x86-artifact': 'builds/releng_sub_%s_configs/%s_x86_artifact.py',
         'api-16-partner-sample1': 'builds/releng_sub_%s_configs/%s_api_16_partner_sample1.py',
         'aarch64': 'builds/releng_sub_%s_configs/%s_aarch64.py',
         'android-test': 'builds/releng_sub_%s_configs/%s_test.py',
         'android-checkstyle': 'builds/releng_sub_%s_configs/%s_checkstyle.py',
         'android-lint': 'builds/releng_sub_%s_configs/%s_lint.py',
         'android-findbugs': 'builds/releng_sub_%s_configs/%s_findbugs.py',
+        'android-geckoview-docs': 'builds/releng_sub_%s_configs/%s_geckoview_docs.py',
         'valgrind' : 'builds/releng_sub_%s_configs/%s_valgrind.py',
         'artifact': 'builds/releng_sub_%s_configs/%s_artifact.py',
         'debug-artifact': 'builds/releng_sub_%s_configs/%s_debug_artifact.py',
         'devedition': 'builds/releng_sub_%s_configs/%s_devedition.py',
         'dmd': 'builds/releng_sub_%s_configs/%s_dmd.py',
     }
     build_pool_cfg_file = 'builds/build_pool_specifics.py'
     branch_cfg_file = 'builds/branch_specifics.py'
--- a/toolkit/components/reputationservice/LoginReputation.cpp
+++ b/toolkit/components/reputationservice/LoginReputation.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "LoginReputation.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsThreadUtils.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/URIUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #define PREF_PP_ENABLED               "browser.safebrowsing.passwords.enabled"
 #define PREF_PASSWORD_ALLOW_TABLE     "urlclassifier.passwordAllowTable"
@@ -401,43 +402,62 @@ LoginReputationService::QueryLoginWhitel
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ENSURE_ARG_POINTER(aRequest);
 
   if (gShuttingDown) {
     return NS_ERROR_ABORT;
   }
 
+  using namespace mozilla::Telemetry;
+  TimeStamp startTimeMs = TimeStamp::Now();
+
   RefPtr<LoginReputationService> self = this;
 
   mLoginWhitelist->QueryLoginWhitelist(aRequest->mParam)->Then(
     GetCurrentThreadSerialEventTarget(), __func__,
-    [self, aRequest](VerdictType aResolveValue) -> void {
+    [self, aRequest, startTimeMs](VerdictType aResolveValue) -> void {
       // Promise is resolved if url is found in google-provided whitelist.
       MOZ_ASSERT(NS_IsMainThread());
       MOZ_ASSERT(aResolveValue == nsILoginReputationVerdictType::SAFE);
 
       LR_LOG(("Query login whitelist [request = %p, result = SAFE]",
               aRequest));
 
+      AccumulateDelta_impl<Millisecond>::compute(
+        LOGIN_REPUTATION_LOGIN_WHITELIST_LOOKUP_TIME, startTimeMs);
+
+      Accumulate(LOGIN_REPUTATION_LOGIN_WHITELIST_RESULT,
+        nsILoginReputationVerdictType::SAFE);
+
       self->Finish(aRequest, NS_OK, nsILoginReputationVerdictType::SAFE);
     },
-    [self, aRequest](nsresult rv) -> void {
+    [self, aRequest, startTimeMs](nsresult rv) -> void {
       // Promise is rejected if url cannot be found in google-provided whitelist.
       // or there is an error.
-      if (LR_LOG_ENABLED()) {
-        if (NS_FAILED(rv)) {
+      if (NS_FAILED(rv)) {
+        if (LR_LOG_ENABLED()) {
           nsAutoCString errorName;
           mozilla::GetErrorName(rv, errorName);
           LR_LOG(("Error in QueryLoginWhitelist() [request = %p, rv = %s]",
                   aRequest, errorName.get()));
-        } else {
-          LR_LOG(("Query login whitelist cannot find the URL [request = %p]",
-                  aRequest));
         }
+
+        // Don't record the lookup time when there is an error, only record the
+        // result here.
+        Accumulate(LOGIN_REPUTATION_LOGIN_WHITELIST_RESULT, 2); // 2 is error
+      } else {
+        AccumulateDelta_impl<Millisecond>::compute(
+          LOGIN_REPUTATION_LOGIN_WHITELIST_LOOKUP_TIME, startTimeMs);
+
+        Accumulate(LOGIN_REPUTATION_LOGIN_WHITELIST_RESULT,
+          nsILoginReputationVerdictType::UNSPECIFIED);
+
+        LR_LOG(("Query login whitelist cannot find the URL [request = %p]",
+                aRequest));
       }
 
       // Check trust-based whitelisting if we can't find the url in login whitelist
       self->Finish(aRequest, rv, nsILoginReputationVerdictType::UNSPECIFIED);
     });
 
   return NS_OK;
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7046,16 +7046,37 @@
   "LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "40",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Time to fetch LocalStorage data before we can expose them as session only data (ms)"
   },
+  "LOGIN_REPUTATION_LOGIN_WHITELIST_RESULT": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["dlee@mozilla.com", "safebrowsing-telemetry@mozilla.org"],
+    "expires_in_version": "never",
+    "releaseChannelCollection": "opt-out",
+    "kind": "enumerated",
+    "bug_numbers": [1422671],
+    "n_values": 3,
+    "description": "Login reputation login whitelist result (0=UNSPECIFIED, 1=SAFE, 2=ERROR)"
+  },
+  "LOGIN_REPUTATION_LOGIN_WHITELIST_LOOKUP_TIME": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["dlee@mozilla.com", "safebrowsing-telemetry@mozilla.org"],
+    "expires_in_version": "never",
+    "releaseChannelCollection": "opt-out",
+    "kind": "exponential",
+    "bug_numbers": [1422671],
+    "high": 5000,
+    "n_buckets": 30,
+    "description": "Time spent per login reputation service lookup local whitelist (ms)"
+  },
   "RANGE_CHECKSUM_ERRORS": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["perf-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Number of histograms with range checksum errors"
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -25,16 +25,24 @@ support-files =
   silentAudioTrack.webm
   doggy.png
   firebird.png
 
 [browser_audioCompeting.js]
 tags = audiochannel
 [browser_audioCompeting_onlyForActiveAgent.js]
 tags = audiochannel
+[browser_autoplay_policy_iframe_hierarchy.js]
+support-files =
+  file_autoplay_three_layers_frame1.html
+  file_autoplay_three_layers_frame2.html
+  file_autoplay_two_layers_frame1.html
+  file_autoplay_two_layers_frame2.html
+  file_video.html
+  gizmo.mp4
 [browser_autoplay_policy_play_twice.js]
 support-files =
   gizmo.mp4
   file_video.html
 [browser_autoplay_policy_user_gestures.js]
 support-files =
   gizmo.mp4
   file_video.html
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_autoplay_policy_iframe_hierarchy.js
@@ -0,0 +1,168 @@
+/**
+ * Test whether the autoplay permission can be transfer well through different
+ * frame hierarchy. When the target frame has been interacted with the user
+ * gesture, it would has the autoplay permission, then the permission would be
+ * propagated to its parent and child frames.
+ *
+ * In this test, I use A/B/C to indicate different domain frames, and the number
+ * after the name is which layer the frame is in.
+ * Ex. A1 -> B2 -> A3,
+ * Top frame and grandchild frame is in the domain A, and child frame is in the
+ * domain B.
+ *
+ * Child frames could get permission if they have same origin as target frame's
+ * Parent frames could get permission if they have same origin as target frame's
+ * or the frame is in the top level.
+ * Ex. A1 -> B2 -> B3,
+ * A1 will always be activated no matter which level frame user activates with,
+ * since it's in the top level.
+ * B2/B3 will only be activated when user activates frame B2 or B3.
+ */
+const PAGE_A1_A2 = "https://example.com/browser/toolkit/content/tests/browser/file_autoplay_two_layers_frame1.html";
+const PAGE_A1_B2 = "https://example.com/browser/toolkit/content/tests/browser/file_autoplay_two_layers_frame2.html";
+const PAGE_A1_B2_C3 = "https://test1.example.org/browser/toolkit/content/tests/browser/file_autoplay_three_layers_frame1.html";
+const PAGE_A1_B2_A3 = "https://example.org/browser/toolkit/content/tests/browser/file_autoplay_three_layers_frame1.html";
+const PAGE_A1_B2_B3 = "https://example.org/browser/toolkit/content/tests/browser/file_autoplay_three_layers_frame2.html";
+const PAGE_A1_A2_A3 = "https://example.com/browser/toolkit/content/tests/browser/file_autoplay_three_layers_frame2.html";
+const PAGE_A1_A2_B3 = "https://example.com/browser/toolkit/content/tests/browser/file_autoplay_three_layers_frame1.html";
+
+function setup_test_preference() {
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["media.autoplay.enabled", false],
+    ["media.autoplay.enabled.user-gestures-needed", true]
+  ]});
+}
+
+var frameTestArray = [
+  { name: "A1_A2",    layersNum: 2, src: PAGE_A1_A2 },
+  { name: "A1_B2",    layersNum: 2, src: PAGE_A1_B2 },
+  { name: "A1_B2_C3", layersNum: 3, src: PAGE_A1_B2_C3 },
+  { name: "A1_B2_A3", layersNum: 3, src: PAGE_A1_B2_A3 },
+  { name: "A1_B2_B3", layersNum: 3, src: PAGE_A1_B2_B3 },
+  { name: "A1_A2_A3", layersNum: 3, src: PAGE_A1_A2_A3 },
+  { name: "A1_A2_B3", layersNum: 3, src: PAGE_A1_A2_B3 }
+];
+
+async function test_permission_propagation(testName, testSrc, layersNum) {
+  info(`- start test for ${testName} -`);
+  for (let layerIdx = 1; layerIdx <= layersNum; layerIdx++) {
+    info("- open new tab -");
+    let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser,
+                                                          "about:blank");
+    tab.linkedBrowser.loadURI(testSrc);
+    await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+    // If the frame isn't activated, the video play will fail.
+    async function playing_video_should_fail(layersNum) {
+      for (let layerIdx = 1; layerIdx <= layersNum; layerIdx++) {
+        let doc;
+        if (layerIdx == 1) {
+          doc = content.document;
+        } else {
+          doc = layerIdx == 2 ? content.frames[0].document :
+                                content.frames[0].frames[0].document;
+        }
+        await doc.getElementById("v").play().catch(function() {
+          ok(true, `video in layer ${layerIdx} can't start play without user input.`);
+        });
+      }
+    }
+    await ContentTask.spawn(tab.linkedBrowser, layersNum,
+                            playing_video_should_fail);
+
+    function activate_frame(testInfo) {
+      let layerIdx = testInfo[0];
+      info(`- activate frame in layer ${layerIdx} (${testInfo[1]}) -`);
+      let doc;
+      if (layerIdx == 1) {
+        doc = content.document;
+      } else {
+        doc = layerIdx == 2 ? content.frames[0].document :
+                              content.frames[0].frames[0].document;
+      }
+      doc.notifyUserActivation();
+    }
+    await ContentTask.spawn(tab.linkedBrowser, [layerIdx, testName],
+                            activate_frame);
+
+    // If frame is activated, the video play will succeed.
+    async function playing_video_may_success(testInfo) {
+      let activeLayerIdx = testInfo[0];
+      let testName = testInfo[1];
+      let layersNum = testInfo[2];
+      for (let layerIdx = 1; layerIdx <= layersNum; layerIdx++) {
+        let doc;
+        if (layerIdx == 1) {
+          doc = content.document;
+        } else {
+          doc = layerIdx == 2 ? content.frames[0].document :
+                                content.frames[0].frames[0].document;
+        }
+        let video = doc.getElementById("v");
+        let shouldSuccess = false;
+        let isActiveLayer = layerIdx == activeLayerIdx;
+        switch (testName) {
+          case "A1_A2":
+          case "A1_A2_A3":
+            // always success to play.
+            shouldSuccess = true;
+            break;
+          case "A1_B2":
+            shouldSuccess = layerIdx == 1 ||
+                            (layerIdx == 2 && isActiveLayer);
+            break;
+          case "A1_B2_C3":
+            shouldSuccess = layerIdx == 1 ||
+                            (layerIdx >= 2 && isActiveLayer);
+            break;
+          case "A1_B2_A3":
+            shouldSuccess = layerIdx != 2 ||
+                            (layerIdx == 2 && isActiveLayer);
+            break;
+          case "A1_B2_B3":
+            shouldSuccess = layerIdx == 1 ||
+                            (layerIdx >= 2 && activeLayerIdx != 1);
+            break;
+          case "A1_A2_B3":
+            shouldSuccess = layerIdx <= 2 ||
+                            (layerIdx == 3 && isActiveLayer);
+            break;
+          default:
+            ok(false, "wrong test name.");
+            break;
+        }
+        try {
+          await video.play();
+          ok(shouldSuccess, `video in layer ${layerIdx} starts playing.`);
+        } catch (e) {
+          ok(!shouldSuccess, `video in layer ${layerIdx} fails to start.`);
+        }
+      }
+    }
+    await ContentTask.spawn(tab.linkedBrowser,
+                            [layerIdx, testName, layersNum],
+                            playing_video_may_success);
+
+    info("- remove tab -");
+    await BrowserTestUtils.removeTab(tab);
+  }
+}
+
+add_task(async function start_test() {
+  info("- setup test preference -");
+  await setup_test_preference();
+  requestLongerTimeout(2);
+
+  info("- test permission propagation in different frame hierarchy -");
+  for (let testIdx = 0; testIdx < frameTestArray.length; testIdx++) {
+    let testInfo = frameTestArray[testIdx];
+    let testName = testInfo.name;
+    let testSrc = testInfo.src;
+    let layersNum = testInfo.layersNum;
+    if (layersNum > 3) {
+      ok(false, "Not support more than 3 layers frame yet.");
+    } else {
+      await test_permission_propagation(testName, testSrc, layersNum);
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_autoplay_three_layers_frame1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+  <title>three frame layers structure 1</title>
+</head>
+<body>
+<!--
+Embed an iframe which has two layers frames, top frame is on domain "example.com",
+child frame is on domain "example.org".
+-->
+</body>
+<video id="v" src="gizmo.mp4" controls loop></video>
+<iframe id="i" src="https://example.com/browser/toolkit/content/tests/browser/file_autoplay_two_layers_frame2.html"
+        height="600" width="800"></iframe></br>
+<script type="text/javascript">
+</script>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_autoplay_three_layers_frame2.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+  <title>three frame layers structure 2</title>
+</head>
+<body>
+<!--
+Embed an iframe which has two layers frames, both frames are in the domain
+"example.com".
+-->
+</body>
+<video id="v" src="gizmo.mp4" controls loop></video>
+<iframe id="i" src="https://example.com/browser/toolkit/content/tests/browser/file_autoplay_two_layers_frame1.html"
+        height="600" width="800"></iframe></br>
+<script type="text/javascript">
+</script>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_autoplay_two_layers_frame1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+  <title>two frame layers structure1</title>
+</head>
+<body>
+<!--
+Embed an iframe which would always in the same domain as this frame.
+-->
+</body>
+<video id="v" src="gizmo.mp4" controls loop></video>
+<iframe id="i" src="file_video.html"
+        height="600" width="800"></iframe></br>
+<script type="text/javascript">
+</script>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_autoplay_two_layers_frame2.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+  <title>two frame layers structure2</title>
+</head>
+<body>
+<!--
+Embed an iframe which is from domain "example.org". This file doesn't guarantee
+all frames would be in the same domain, it depend on what domain we put this file
+on.
+-->
+</body>
+<video id="v" src="gizmo.mp4" controls loop></video>
+<iframe id="i" src="https://example.org/browser/toolkit/content/tests/browser/file_video.html"
+        height="600" width="800"></iframe></br>
+<script type="text/javascript">
+</script>
+</html>
\ No newline at end of file