merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 13 Sep 2016 11:57:54 +0200
changeset 354935 f5d043ce6d36a3c461cbd829d4a4a38394b7c436
parent 354843 bdf56e1152aa2678b498cfcc0c0a36728c0cefa7 (current diff)
parent 354934 aa63a2a40325d64c9121139d40c0170bda1e5482 (diff)
child 354939 f4f31981649796c0e1f3b2e171995f75bbdd339f
child 355013 bf23ff06c7cdcc66eb1a4d4a4db4637a3d7bd94f
child 355095 2320bcd12315f543b46ea2fb9a41d1fe202fae0d
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
first release with
nightly linux32
f5d043ce6d36 / 51.0a1 / 20160913030425 / files
nightly linux64
f5d043ce6d36 / 51.0a1 / 20160913030425 / files
nightly mac
f5d043ce6d36 / 51.0a1 / 20160913030425 / files
nightly win32
f5d043ce6d36 / 51.0a1 / 20160913030425 / files
nightly win64
f5d043ce6d36 / 51.0a1 / 20160913030425 / 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 mozilla-inbound to mozilla-central a=merge
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1287426 - Clobber required because of Linux Chromium sandbox file moves.
+Bug 1288460 requires another clobber due to bug 1298779.
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -253,16 +253,34 @@ EventTree::FindOrInsert(Accessible* aCon
     while (parent) {
       // Reached a top, no match for a current event.
       if (parent == top) {
         break;
       }
 
       // We got a match.
       if (parent->Parent() == node->mContainer) {
+        // Reject the node if it's contained by a show/hide event target
+        uint32_t evCount = node->mDependentEvents.Length();
+        for (uint32_t idx = 0; idx < evCount; idx++) {
+          AccMutationEvent* ev = node->mDependentEvents[idx];
+          if (ev->GetAccessible() == parent) {
+#ifdef A11Y_LOG
+            if (logging::IsEnabledAll(logging::eEventTree |
+                                      logging::eVerbose)) {
+              logging::MsgBegin("EVENTS_TREE",
+                "Rejecting node since contained by show/hide target");
+              logging::AccessibleInfo("Container", aContainer);
+              logging::MsgEnd();
+            }
+#endif
+            return nullptr;
+          }
+        }
+
         return node->FindOrInsert(aContainer);
       }
 
       parent = parent->Parent();
       MOZ_ASSERT(parent, "Wrong tree");
     }
 
     // If the given container contains a current node
--- a/accessible/tests/mochitest/events/test_coalescence.html
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -441,16 +441,57 @@
         testIsDefunct(this.b);
       }
 
       this.getID = function test5_getID() {
         return "remove a child, remove a parent sibling, remove the parent";
       }
     }
 
+    /**
+     * Insert accessibles with a child node moved by aria-owns
+     * Markup:
+     * <div id="t6_fc">
+     *   <div id="t6_owns"></div>
+     * </div>
+     * <div id="t6_sc" aria-owns="t6_owns"></div>
+     */
+    function test6()
+    {
+      this.parent = getNode("t6");
+      this.fc = document.createElement("div");
+      this.fc.setAttribute("id", "t6_fc");
+      this.owns = document.createElement("div");
+      this.owns.setAttribute("id", "t6_owns");
+      this.sc = document.createElement("div");
+      this.sc.setAttribute("id", "t6_sc");
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, this.fc),
+        new invokerChecker(EVENT_SHOW, this.sc),
+        new invokerChecker(EVENT_REORDER, this.parent),
+        new unexpectedInvokerChecker(EVENT_REORDER, this.fc),
+        new unexpectedInvokerChecker(EVENT_REORDER, this.sc),
+        new unexpectedInvokerChecker(EVENT_HIDE, this.owns),
+        new unexpectedInvokerChecker(EVENT_SHOW, this.owns)
+      ];
+
+      this.invoke = function test6_invoke()
+      {
+        getNode("t6").appendChild(this.fc);
+        getNode("t6_fc").appendChild(this.owns);
+        getNode("t6").appendChild(this.sc);
+        getNode("t6_sc").setAttribute("aria-owns", "t6_owns");
+      };
+
+      this.getID = function test6_getID() {
+        return "Insert accessibles with a child node moved by aria-owns";
+      };
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests.
 
     //gA11yEventDumpToConsole = true; // debug stuff
     //enableLogging("tree,eventTree,verbose");
 
     var gQueue = null;
     function doTests()
@@ -472,16 +513,17 @@
       gQueue.push(new showParentNChild("select10", "option10", true));
       gQueue.push(new showParentNAddChild("select11", false));
       gQueue.push(new showParentNAddChild("select12", true));
 
       gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
       gQueue.push(new test3());
       gQueue.push(new test4());
       gQueue.push(new test5());
+      gQueue.push(new test6());
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -562,10 +604,13 @@
   </div>
 
   <div id="t5">
     <div role="button" id="t5_b">btn</div>
     <div role="listbox" id="t5_lb">
       <div role="option" id="t5_o">opt</div>
     </div>
   </div>
+
+  <div id="t6">
+  </div>
 </body>
 </html>
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -420,22 +420,16 @@ Section "-Application" APP_IDX
     StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
     ${If} $AddDesktopSC == 1
     ${OrIf} $AddStartMenuSC == 1
     ${OrIf} $AddTaskbarSC == 1
       WriteRegDWORD HKCU "$0" "IconsVisible" 1
     ${Else}
       WriteRegDWORD HKCU "$0" "IconsVisible" 0
     ${EndIf}
-  ${If} ${AtLeastWin8}
-    ${RemoveDEHRegistration} ${DELEGATE_EXECUTE_HANDLER_ID} \
-                             $AppUserModelID \
-                             "FirefoxURL" \
-                             "FirefoxHTML"
-  ${EndIf}
   ${EndIf}
 
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; If the maintenance service page was displayed then a value was already 
   ; explicitly selected for installing the maintenance service and 
   ; and so InstallMaintenanceService will already be 0 or 1.
   ; If the maintenance service page was not displayed then 
   ; InstallMaintenanceService will be equal to "".
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -1,35 +1,13 @@
 # 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/.
 
-; The registration ID of the COM server which is used for choosing wether
-; to launch the Win8 metro browser or desktop browser.
-!define DELEGATE_EXECUTE_HANDLER_ID {5100FEC1-212B-4BF5-9BF8-3E650FD794A3}
-;
-; Defines for adjust token privs and for enumerating keys
-!ifndef TOKEN_QUERY
-  !define TOKEN_QUERY             0x0008
-!endif
-!ifndef TOKEN_ADJUST_PRIVILEGES
-  !define TOKEN_ADJUST_PRIVILEGES 0x0020
-!endif
-!ifndef SE_RESTORE_NAME
-  !define SE_RESTORE_NAME         SeRestorePrivilege
-!endif
-!ifndef SE_PRIVILEGE_ENABLED
-  !define SE_PRIVILEGE_ENABLED    0x00000002
-!endif
-!ifndef HKEY_USERS
-  !define HKEY_USERS              0x80000003
-!endif
-
 !macro PostUpdate
-
   ; PostUpdate is called from both session 0 and from the user session
   ; for service updates, make sure that we only register with the user session
   ; Otherwise ApplicationID::Set can fail intermittently with a file in use error.
   System::Call "kernel32::GetCurrentProcessId() i.r0"
   System::Call "kernel32::ProcessIdToSessionId(i $0, *i ${NSIS_MAX_STRLEN} r9)"
 
   ; Determine if we're the protected UserChoice default or not. If so fix the
   ; start menu tile.  In case there are 2 Firefox installations, we only do
@@ -186,24 +164,16 @@
       ; based on the above checks, so attempt to just run the EXE.
       ; In the worst case, in case there is some edge case with the
       ; IsAdmin check and the permissions check, the maintenance service
       ; will just fail to be attempted to be installed.
       nsExec::Exec "$\"$INSTDIR\maintenanceservice_installer.exe$\""
     ${EndIf}
   ${EndIf}
 !endif
-
-; Register the DEH
-${If} ${AtLeastWin8}
-  ${RemoveDEHRegistration} ${DELEGATE_EXECUTE_HANDLER_ID} \
-                           $AppUserModelID \
-                           "FirefoxURL" \
-                           "FirefoxHTML"
-${EndIf}
 !macroend
 !define PostUpdate "!insertmacro PostUpdate"
 
 !macro SetAsDefaultAppGlobal
   ${RemoveDeprecatedKeys} ; Does not use SHCTX
 
   SetShellVarContext all      ; Set SHCTX to all users (e.g. HKLM)
   ${SetHandlers} ; Uses SHCTX
@@ -826,178 +796,16 @@
   StrCpy $0 "Software\Microsoft\Windows\Shell\Associations\UrlAssociations\gopher"
   ReadRegStr $2 HKCU "$0\UserChoice" "Progid"
   ${If} "$2" == "FirefoxURL"
     DeleteRegKey HKCU "$0"
   ${EndIf}
 !macroend
 !define RemoveDeprecatedKeys "!insertmacro RemoveDeprecatedKeys"
 
-; Resets Win8+ specific toast keys Windows sets. We call this on a
-; fresh install and on uninstall.
-!macro ResetWin8PromptKeys KEY PREFIX
-  ${If} ${AtLeastWin8}
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxHTML_.htm"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxHTML_.html"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxHTML_.xht"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxHTML_.xhtml"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxHTML_.shtml"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxURL_ftp"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxURL_http"
-    DeleteRegValue ${KEY} "${PREFIX}Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "FirefoxURL_https"
-  ${EndIf}
-!macroend
-!define ResetWin8PromptKeys "!insertmacro ResetWin8PromptKeys"
-
-; Adds SE_RESTORE_NAME privs
-!macro AcquireSERestoreName
-  StrCpy $R1 0
-
-  System::Call "kernel32::GetCurrentProcess() i .R0"
-  System::Call "advapi32::OpenProcessToken(i R0, i ${TOKEN_QUERY}|${TOKEN_ADJUST_PRIVILEGES}, \
-                                          *i R1R1) i .R0"
-  ${If} $R0 != 0
-    System::Call "advapi32::LookupPrivilegeValue(t n, t '${SE_RESTORE_NAME}', *l .R2) i .R0"
-    ${If} $R0 != 0
-      System::Call "*(i 1, l R2, i ${SE_PRIVILEGE_ENABLED}) i .R0"
-      System::Call "advapi32::AdjustTokenPrivileges(i R1, i 0, i R0, i 0, i 0, i 0)"
-      System::Free $R0
-    ${EndIf}
-    System::Call "kernel32::CloseHandle(i R1)"
-  ${EndIf}
-!macroend
-!define AcquireSERestoreName "!insertmacro AcquireSERestoreName"
-!define un.AcquireSERestoreName "!insertmacro AcquireSERestoreName"
-
-; Mounts all user ntuser.dat files into the registry as a subkey of HKU
-!macro MountRegistryIntoHKU
-  ; $0 is used as an index for HKEY_USERS enumeration
-  StrCpy $0 0
-  ${Do}
-    EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
-    ${If} $1 == ""
-      ${Break}
-    ${EndIf}
-    ReadRegStr $2 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath"
-    System::Call "advapi32::RegLoadKey(i ${HKEY_USERS}, t 'User-$0', t '$2\ntuser.dat')"
-    System::Call "advapi32::RegLoadKey(i ${HKEY_USERS}, t 'User-$0_Classes', t '$2\AppData\Local\Microsoft\Windows\UsrClass.dat')"
-    IntOp $0 $0 + 1
-  ${Loop}
-!macroend
-!define MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
-!define un.MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
-
-; Unmounts all user ntuser.dat files into the registry as a subkey of HKU
-!macro UnmountRegistryIntoHKU
-  ; $0 is used as an index for HKEY_USERS enumeration
-  StrCpy $0 0
-  ${Do}
-    EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
-    ${If} $1 == ""
-      ${Break}
-    ${EndIf}
-    ReadRegStr $2 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath"
-    System::Call "advapi32::RegUnLoadKey(i ${HKEY_USERS}, t 'User-$0')"
-    System::Call "advapi32::RegUnLoadKey(i ${HKEY_USERS}, t 'User-$0_Classes')"
-    IntOp $0 $0 + 1
-  ${Loop}
-!macroend
-!define UnmountRegistryIntoHKU "!insertmacro UnmountRegistryIntoHKU"
-!define un.UnmountRegistryIntoHKU "!insertmacro UnmountRegistryIntoHKU"
-
-; Unconditionally removes the delegate execute handler registration used to
-; launch the metro browser and misc. metro related registry values.
-!macro RemoveDEHRegistration DELEGATE_EXECUTE_HANDLER_ID \
-                             APP_USER_MODEL_ID \
-                             PROTOCOL_ACTIVATION_ID \
-                             FILE_ACTIVATION_ID
-  ${AcquireSERestoreName}
-  ${MountRegistryIntoHKU}
-
-  ; $0 is used as an index for HKEY_USERS enumeration
-  StrCpy $0 0
-
-  ${Do}
-    EnumRegKey $1 HKU "" $0
-    ${If} $1 == ""
-      ${Break}
-    ${EndIf}
-
-    ClearErrors
-    ${WordFind} "$1" "_Classes" "E#" $3
-    ${Unless} ${Errors}
-      ; remove the app user model id root registration. We don't need this
-      ; here anymore, we just use it for tray registrationdown in widget,
-      ; which we read out of the mozilla keys.
-      ${If} "${APP_USER_MODEL_ID}" != ""
-        ; The removal of this key intermittently fails, so do the best we can in cleanup
-        DeleteRegValue HKU "$1\${APP_USER_MODEL_ID}\.exe\shell\open\command" "DelegateExecute"
-        DeleteRegKey HKU "$1\${APP_USER_MODEL_ID}\.exe\shell\open"
-        DeleteRegKey HKU "$1\${APP_USER_MODEL_ID}\.exe\shell"
-        DeleteRegKey HKU "$1\${APP_USER_MODEL_ID}\.exe"
-        DeleteRegKey HKU "$1\\${APP_USER_MODEL_ID}"
-      ${EndIf}
-      ;
-      ; Remove delegate execute handler clsid registration
-      DeleteRegKey HKU "$1\CLSID\${DELEGATE_EXECUTE_HANDLER_ID}"
-
-      ; Remove protocol and file delegate execute handler id assoc
-      DeleteRegValue HKU "$1\${PROTOCOL_ACTIVATION_ID}" "AppUserModelID"
-      DeleteRegValue HKU "$1\${FILE_ACTIVATION_ID}" "AppUserModelID"
-
-      ; Remove delegate execute application registry keys
-      DeleteRegKey HKU "$1\${PROTOCOL_ACTIVATION_ID}\Application"
-      DeleteRegKey HKU "$1\${FILE_ACTIVATION_ID}\Application"
-
-      ; Remove misc. shell open info
-      DeleteRegValue HKU "$1\${PROTOCOL_ACTIVATION_ID}\shell\open" "CommandId"
-      DeleteRegValue HKU "$1\${FILE_ACTIVATION_ID}\shell\open" "CommandId"
-      DeleteRegValue HKU "$1\${PROTOCOL_ACTIVATION_ID}\shell\open\command" "DelegateExecute"
-      DeleteRegValue HKU "$1\${FILE_ACTIVATION_ID}\shell\open\command" "DelegateExecute"
-
-      ; remove metro browser splash image data
-      DeleteRegKey HKU "$1\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\DefaultBrowser_NOPUBLISHERID\SplashScreen\DefaultBrowser_NOPUBLISHERID!${APP_USER_MODEL_ID}"
-    ${Else}
-      ; misc. Metro keys
-      DeleteRegKey HKU "$1\Software\Mozilla\Firefox\Metro"
-      DeleteRegValue HKU "$1\Software\Mozilla\Firefox" "CEHDump"
-      DeleteRegValue HKU "$1\Software\Mozilla\Firefox" "MetroD3DAvailable"
-      DeleteRegValue HKU "$1\Software\Mozilla\Firefox" "MetroLastAHE"
-      ${ResetWin8PromptKeys} "HKU" "$1\"
-    ${EndIf}
-    IntOp $0 $0 + 1
-  ${Loop}
-  ${UnmountRegistryIntoHKU}
-
-  ; The removal of this key intermittently fails, so do the best we can in cleanup
-  ${If} "${APP_USER_MODEL_ID}" != ""
-    DeleteRegValue HKLM "Software\Classes\${APP_USER_MODEL_ID}\.exe\shell\open\command" "DelegateExecute"
-    DeleteRegKey HKLM "Software\Classes\${APP_USER_MODEL_ID}\.exe\shell\open"
-    DeleteRegKey HKLM "Software\Classes\${APP_USER_MODEL_ID}\.exe\shell"
-    DeleteRegKey HKLM "Software\Classes\${APP_USER_MODEL_ID}\.exe"
-    DeleteRegKey HKLM "Software\Classes\${APP_USER_MODEL_ID}"
-  ${EndIf}
-
-  ; Remove HKLM entries
-  DeleteRegKey HKLM "Software\Classes\CLSID\${DELEGATE_EXECUTE_HANDLER_ID}"
-  DeleteRegKey HKLM "Software\Classes\${PROTOCOL_ACTIVATION_ID}\Application"
-  DeleteRegKey HKLM "Software\Classes\${FILE_ACTIVATION_ID}\Application"
-  DeleteRegValue HKLM "Software\Classes\${PROTOCOL_ACTIVATION_ID}\shell\open\command" "DelegateExecute"
-  DeleteRegValue HKLM "Software\Classes\${FILE_ACTIVATION_ID}\shell\open\command" "DelegateExecute"
-  DeleteRegValue HKLM "Software\Classes\${PROTOCOL_ACTIVATION_ID}" "AppUserModelID"
-  DeleteRegValue HKLM "Software\Classes\${FILE_ACTIVATION_ID}" "AppUserModelID"
-  DeleteRegValue HKLM "Software\Classes\${PROTOCOL_ACTIVATION_ID}\shell\open" "CommandId"
-  DeleteRegValue HKLM "Software\Classes\${FILE_ACTIVATION_ID}\shell\open" "CommandId"
-
-  ClearErrors
-!macroend
-
-!define RemoveDEHRegistration "!insertmacro RemoveDEHRegistration"
-!define un.RemoveDEHRegistration "!insertmacro RemoveDEHRegistration"
-
 ; Removes various directories and files for reasons noted below.
 !macro RemoveDeprecatedFiles
   ; Some users are ending up with unpacked chrome instead of omni.ja. This
   ; causes Firefox to break badly after upgrading from Firefox 31, see bug
   ; 1063052. Removing the chrome.manifest from the install directory causes
   ; Firefox to use the updated omni.ja so it won't crash.
   ${If} ${FileExists} "$INSTDIR\chrome.manifest"
     Delete "$INSTDIR\chrome.manifest"
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -273,23 +273,16 @@ Section "Uninstall"
     DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
     StrCpy $TmpVal "HKLM" ; used primarily for logging
     ${un.RegCleanMain} "Software\Mozilla"
     ${un.RegCleanUninstall}
     ${un.DeleteShortcuts}
     ${un.SetAppLSPCategories}
   ${EndIf}
 
-  ${If} ${AtLeastWin8}
-    ${RemoveDEHRegistration} ${DELEGATE_EXECUTE_HANDLER_ID} \
-                             $AppUserModelID \
-                             "FirefoxURL" \
-                             "FirefoxHTML"
-  ${EndIf}
-
   ${un.RegCleanAppHandler} "FirefoxURL"
   ${un.RegCleanAppHandler} "FirefoxHTML"
   ${un.RegCleanProtocolHandler} "ftp"
   ${un.RegCleanProtocolHandler} "http"
   ${un.RegCleanProtocolHandler} "https"
 
   ClearErrors
   ReadRegStr $R9 HKCR "FirefoxHTML" ""
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -28,16 +28,19 @@
 
   --toolbarbutton-active-background: rgba(154,154,154,.5) linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.4));
   --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
   --toolbarbutton-active-boxshadow: 0 1px 1px rgba(0,0,0,.1) inset, 0 0 1px rgba(0,0,0,.3) inset;
 
   --toolbarbutton-checkedhover-backgroundcolor: rgba(200,200,200,.5);
 
   --panel-separator-color: ThreeDShadow;
+  --arrowpanel-dimmed: hsla(210,4%,10%,.07);
+  --arrowpanel-dimmed-further: hsla(210,4%,10%,.12);
+  --arrowpanel-dimmed-even-further: hsla(210,4%,10%,.17);
 
   --urlbar-separator-color: ThreeDShadow;
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
@@ -990,35 +993,35 @@ toolbaritem[cui-areatype="menu-panel"] >
 }
 
 #urlbar-zoom-button > .toolbarbutton-icon {
   display: none;
 }
 
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings {
   -moz-appearance: none;
   -moz-user-focus: ignore;
   color: GrayText;
   margin: 0;
   border: 0;
   padding: 8px 20px;
   background: transparent;
 }
 
 #urlbar-search-settings:hover {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings:hover:active {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 #urlbar-search-splitter {
   -moz-appearance: none;
   width: 8px;
   margin-inline-start: -4px;
 }
 
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -132,17 +132,17 @@ menuitem[cmd="cmd_clearhistory"][disable
 }
 
 .search-panel-tree {
   border-top: 1px solid var(--panel-separator-color) !important;
 }
 
 .search-panel-header {
   font-weight: normal;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   border: none;
   border-top: 1px solid var(--panel-separator-color);
   padding: 3px 5px;
   color: GrayText;
 }
 
 .search-panel-header > label {
   margin-top: 2px !important;
@@ -296,17 +296,17 @@ menuitem[cmd="cmd_clearhistory"][disable
 
 .search-setting-button {
   -moz-appearance: none;
   margin: 0;
   min-height: 32px;
 }
 
 .search-setting-button[selected] {
-  background-color: hsla(210,4%,10%,.15);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 .search-setting-button-compact {
   list-style-image: url("chrome://browser/skin/gear.svg#gear");
 }
 
 .search-setting-button-compact[selected] {
   list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -39,16 +39,19 @@
   --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
   --urlbar-dropmarker-region: rect(0, 11px, 14px, 0);
   --urlbar-dropmarker-active-region: rect(0, 22px, 14px, 11px);
   --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
   --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
   --urlbar-dropmarker-active-2x-region: rect(0, 44px, 28px, 22px);
 
   --panel-separator-color: hsla(210,4%,10%,.14);
+  --arrowpanel-dimmed: hsla(210,4%,10%,.07);
+  --arrowpanel-dimmed-further: hsla(210,4%,10%,.12);
+  --arrowpanel-dimmed-even-further: hsla(210,4%,10%,.17);
 
   --urlbar-separator-color: hsla(0,0%,16%,.2);
 }
 
 #urlbar:-moz-lwtheme:not([focused="true"]),
 .searchbar-textbox:-moz-lwtheme:not([focused="true"]) {
   opacity: .9;
 }
@@ -1703,33 +1706,33 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 }
 
 #urlbar-zoom-button > .toolbarbutton-icon {
   display: none;
 }
 
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings {
   -moz-appearance: none;
   -moz-user-focus: ignore;
   color: GrayText;
   margin: 0;
   padding: 8px 20px;
 }
 
 #urlbar-search-settings:hover {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings:hover:active {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 #urlbar-search-splitter {
   min-width: 8px;
   width: 8px;
   background-image: none;
   margin: 0 -4px;
   position: relative;
--- a/browser/themes/osx/searchbar.css
+++ b/browser/themes/osx/searchbar.css
@@ -122,17 +122,17 @@
 
 .search-panel-tree {
   border-top: 1px solid var(--panel-separator-color) !important;
 }
 
 .search-panel-header {
   font-size: 10px;
   font-weight: normal;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   border-top: 1px solid var(--panel-separator-color);
   margin: 0;
   padding: 3px 6px;
   color: GrayText;
 }
 
 .search-panel-header > label {
   margin-top: 2px !important;
@@ -278,17 +278,17 @@
 
 .search-setting-button {
   -moz-appearance: none;
   border-radius: 0 0 4px 4px;
   min-height: 32px;
 }
 
 .search-setting-button[selected] {
-  background-color: hsla(210,4%,10%,.15);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 .search-setting-button-compact {
   list-style-image: url("chrome://browser/skin/gear.svg#gear");
 }
 
 .search-setting-button-compact[selected] {
   list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -151,21 +151,21 @@
 }
 
 .identity-popup-expander:-moz-focusring > .button-box,
 .identity-popup-expander[panel-multiview-anchor] > .button-box {
   border-style: none;
 }
 
 .identity-popup-expander:hover {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 .identity-popup-expander:hover:active {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 /* CONTENT */
 
 .identity-popup-permission-label,
 .identity-popup-permission-state-label,
 #identity-popup-security-content > description,
@@ -273,36 +273,36 @@
 }
 
 #identity-popup-securityView-body {
   padding-inline-end: 1em;
 }
 
 #identity-popup-securityView-footer {
   margin-top: 1em;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #identity-popup-securityView-footer > button {
   -moz-appearance: none;
   margin: 0;
   border: none;
   border-top: 1px solid var(--panel-separator-color);
   padding: 8px 20px;
   color: inherit;
   background-color: transparent;
 }
 
 #identity-popup-securityView-footer > button:hover,
 #identity-popup-securityView-footer > button:focus {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #identity-popup-securityView-footer > button:hover:active {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 #identity-popup-content-verifier ~ description {
   margin-top: 1em;
   color: Graytext;
 }
 
 description#identity-popup-content-verified-by,
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -402,17 +402,17 @@ toolbarpaletteitem:-moz-any([place="pale
   width: 16px;
   margin-inline-start: -16px;
   height: 51px;
   margin-bottom: 2.2em;
   padding: 0;
 }
 
 .panelUI-grid .toolbarbutton-1:not([buttonover])@buttonStateHover@ > .toolbarbutton-menubutton-dropmarker {
-  background-color: hsla(210,4%,10%,.1) !important;
+  background-color: var(--arrowpanel-dimmed) !important;
   border-radius: 0 0 0 2px;
 }
 
 .panelUI-grid .toolbarbutton-1:not([buttonover])@buttonStateHover@ > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(rtl) {
   border-radius: 0 0 2px 0;
 }
 
 #main-window:not([customizing]) .panel-combined-button[disabled] > .toolbarbutton-icon {
@@ -429,17 +429,17 @@ toolbaritem[cui-areatype="menu-panel"][s
   -moz-box-pack: center;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] > iframe {
   margin: 4px auto;
 }
 
 #PanelUI-multiView[viewtype="subview"] > .panel-viewcontainer > .panel-viewstack > .panel-mainview >  #PanelUI-mainView {
-  background-color: hsla(210,4%,10%,.1);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .panel-wide-item,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .toolbarbutton-1:not([panel-multiview-anchor="true"]),
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-update-status,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-avatar,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-label,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-icon,
@@ -530,48 +530,48 @@ toolbarpaletteitem[place="palette"] > to
 #zoom-reset-button > .toolbarbutton-icon {
   display: none;
 }
 
 #PanelUI-footer {
   display: flex;
   flex-shrink: 0;
   flex-direction: column;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   padding: 0;
   margin: 0;
 }
 
 #main-window[customizing] #PanelUI-footer-fxa {
   display: none;
 }
 
 #PanelUI-footer-fxa:not([fxastatus="signedin"]) > toolbarseparator,
 #PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-icon,
 #PanelUI-footer-fxa:not([fxaprofileimage]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
   display: none;
 }
 
 #PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status::after {
   content: url(chrome://browser/skin/warning.svg);
-  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+  filter: drop-shadow(0 1px 0 hsla(206,50%,10%,.15));
   width: 47px;
   padding-top: 1px;
   display: block;
   text-align: center;
   position: relative;
   top: 25%;
 }
 
 #PanelUI-update-status[update-status]::after {
   content: "";
   width: 14px;
   height: 14px;
   margin-right: 16.5px;
-  box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px rgba(0, 0, 0, 0.1) inset, 0px 1px 0px rgba(12, 27, 38, 0.2);
+  box-shadow: 0px 1px 0px rgba(255,255,255,.2) inset, 0px -1px 0px rgba(0,0,0,.1) inset, 0px 1px 0px rgba(12,27,38,.2);
   border-radius: 2px;
   background-size: contain;
   display: -moz-box;
 }
 
 #PanelUI-update-status[update-status="succeeded"]::after {
   background-image: url(chrome://browser/skin/update-badge.svg);
   background-color: #74BF43;
@@ -904,77 +904,77 @@ toolbarpaletteitem[place="palette"] > to
   opacity: 0.4;
 }
 
 #PanelUI-fxa-status:not([disabled]):hover,
 #PanelUI-fxa-icon:not([disabled]):hover,
 #PanelUI-help:not([disabled]):hover,
 #PanelUI-customize:hover,
 #PanelUI-quit:not([disabled]):hover {
-  outline: 1px solid hsla(210,4%,10%,.07);
-  background-color: hsla(210,4%,10%,.07);
+  outline: 1px solid var(--arrowpanel-dimmed);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #PanelUI-fxa-status:not([disabled]):hover:active,
 #PanelUI-fxa-icon:not([disabled]):hover:active,
 #PanelUI-help:not([disabled]):hover:active,
 #PanelUI-customize:hover:active,
 #PanelUI-quit:not([disabled]):hover:active {
-  outline: 1px solid hsla(210,4%,10%,.12);
-  background-color: hsla(210,4%,10%,.12);
+  outline: 1px solid var(--arrowpanel-dimmed-further);
+  background-color: var(--arrowpanel-dimmed-further);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 #PanelUI-fxa-status:not([disabled]):hover,
 #PanelUI-fxa-status:not([disabled]):hover:active,
 #PanelUI-fxa-icon:not([disabled]):hover,
 #PanelUI-fxa-icon:not([disabled]):hover:active {
   outline: none;
 }
 
 #PanelUI-footer-fxa[fxastatus="error"] {
-  background-color: hsla(42, 94%, 88%, 1.0);
-  border-top: 1px solid hsla(42, 94%, 70%, 1.0);
+  background-color: hsl(42,94%,88%);
+  border-top: 1px solid hsl(42,94%,70%);
 }
 
 #PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover {
-  background-color: hsla(42, 94%, 85%, 1.0);
+  background-color: hsl(42,94%,85%);
 }
 
 #PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover:active {
-  background-color: hsla(42, 94%, 82%, 1.0);
+  background-color: hsl(42,94%,82%);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 #PanelUI-update-status {
   color: black;
 }
 
 #PanelUI-update-status[update-status="succeeded"] {
-  background-color: hsla(96, 65%, 75%, 0.5);
+  background-color: hsla(96,65%,75%,.5);
 }
 
 #PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover {
-  background-color: hsla(96, 65%, 75%, 0.8);
+  background-color: hsla(96,65%,75%,.8);
 }
 
 #PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover:active {
-  background-color: hsl(96, 65%, 75%);
+  background-color: hsl(96,65%,75%);
 }
 
 #PanelUI-update-status[update-status="failed"] {
-  background-color: hsla(359, 69%, 84%, 0.5);
+  background-color: hsla(359,69%,84%,.5);
 }
 
 #PanelUI-update-status[update-status="failed"]:not([disabled]):hover {
-  background-color: hsla(359, 69%, 84%, 0.8);
+  background-color: hsla(359,69%,84%,.8);
 }
 
 #PanelUI-update-status[update-status="failed"]:not([disabled]):hover:active {
-  background-color: hsl(359, 69%, 84%);
+  background-color: hsl(359,69%,84%);
 }
 
 #PanelUI-quit:not([disabled]):hover {
   background-color: #d94141;
   outline-color: #c23a3a;
 }
 
 #PanelUI-quit:not([disabled]):hover:active {
@@ -1006,20 +1006,20 @@ toolbarpaletteitem[place="palette"] > to
 panelview .toolbarbutton-1,
 .subviewbutton,
 .widget-overflow-list .toolbarbutton-1,
 .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 .share-provider-button,
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
   -moz-appearance: none;
   padding: 0 6px;
-  background-color: hsla(210,4%,10%,0);
+  background-color: transparent;
   border-radius: 2px;
   border-style: solid;
-  border-color: hsla(210,4%,10%,0);
+  border-color: transparent;
 }
 
 panelview .toolbarbutton-1,
 .subviewbutton,
 .widget-overflow-list .toolbarbutton-1,
 .share-provider-button,
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
   border-width: 1px;
@@ -1092,51 +1092,51 @@ panelview .toolbarbutton-1,
 
 panelview .toolbarbutton-1@buttonStateHover@,
 toolbarbutton.subviewbutton@buttonStateHover@,
 menu.subviewbutton@menuStateHover@,
 menuitem.subviewbutton@menuStateHover@,
 .share-provider-button@buttonStateHover@:not([checked="true"]),
 .widget-overflow-list .toolbarbutton-1@buttonStateHover@,
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
-  background-color: hsla(210,4%,10%,.08);
-  border-color: hsla(210,4%,10%,.11);
+  background-color: var(--arrowpanel-dimmed);
+  border-color: var(--panel-separator-color);
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@@buttonStateHover@ {
-  border-color: hsla(210,4%,10%,.11);
+  border-color: var(--panel-separator-color);
 }
 
 panelview .toolbarbutton-1:-moz-any(@buttonStateActive@,[checked=true]),
 toolbarbutton.subviewbutton@buttonStateActive@,
 menu.subviewbutton@menuStateActive@,
 menuitem.subviewbutton@menuStateActive@,
 .share-provider-button:-moz-any(@buttonStateActive@,[checked=true]),
 .widget-overflow-list .toolbarbutton-1@buttonStateActive@,
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
   border-color: var(--panel-separator-color);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
 }
 
 .subviewbutton.panel-subview-footer {
   margin: 4px -4px -4px;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   border-top: 1px solid var(--panel-separator-color);
   border-radius: 0;
 }
 
 menuitem.panel-subview-footer@menuStateHover@,
 .subviewbutton.panel-subview-footer@buttonStateHover@ {
-  background-color: hsla(210,4%,10%,.15);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 menuitem.panel-subview-footer@menuStateActive@,
 .subviewbutton.panel-subview-footer@buttonStateActive@ {
-  background-color: hsla(210,4%,10%,.19);
+  background-color: var(--arrowpanel-dimmed-even-further);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 #BMB_bookmarksPopup .subviewbutton {
   font: menu;
   font-weight: normal;
 }
 
@@ -1293,21 +1293,21 @@ toolbarpaletteitem[place="palette"] > .t
 }
 
 toolbarpaletteitem[place="palette"] > #search-container {
   min-width: 7em;
   width: 7em;
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@ {
-  background-color: hsla(210,4%,10%,0);
+  background-color: transparent;
   border-radius: 2px;
   border: 1px solid;
-  border-color: hsla(210,4%,10%,0);
-  border-bottom-color: hsla(210,4%,10%,.1);
+  border-color: transparent;
+  border-bottom-color: var(--panel-separator-color);
   padding: 0;
   transition-property: background-color, border-color;
   transition-duration: 150ms;
 }
 
 /* Make direct siblings overlap borders: */
 .toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@ {
   border-top-color: transparent !important;
@@ -1476,17 +1476,17 @@ menuitem[checked="true"].subviewbutton >
 }
 
 #PanelUI-panicView.cui-widget-panelview {
   min-width: 280px;
 }
 
 #PanelUI-panic-timeframe {
   padding: 15px;
-  border-bottom: 1px solid rgba(0,0,0,0.1);
+  border-bottom: 1px solid var(--panel-separator-color);
 }
 
 #panic-button-success-icon,
 #PanelUI-panic-timeframe-icon,
 #PanelUI-panic-timeframe-icon-small {
   background-color: transparent;
   margin-inline-end: 10px;
 }
@@ -1535,24 +1535,24 @@ menuitem[checked="true"].subviewbutton >
   padding: 1px;
   margin: 0 0 2px;
   background-color: transparent;
   border-radius: 2px;
   border: 1px solid transparent;
 }
 
 .subviewradio@buttonStateHover@ {
-  background-color: hsla(210,4%,10%,.08);
+  background-color: var(--arrowpanel-dimmed);
   border-color: var(--panel-separator-color);
 }
 
 .subviewradio[selected],
 .subviewradio[selected]:hover,
 .subviewradio@buttonStateActive@ {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
   border-color: var(--panel-separator-color);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
 }
 
 .subviewradio > .radio-check {
   -moz-appearance: none;
   width: 16px;
   height: 16px;
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -36,17 +36,17 @@
   padding: 16px 25px;
   margin: 0;
   /* The panel can be wider than this description after the blocked subview is
      shown, so center the text. */
   text-align: center;
 }
 
 .downloadsPanelFooter {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   border-top: 1px solid var(--panel-separator-color);
 }
 
 .downloadsPanelFooter toolbarseparator {
   margin: 0;
   border: 0;
   min-width: 0;
   border-left: 1px solid var(--panel-separator-color);
@@ -60,24 +60,24 @@
   margin: 0;
   padding: 0;
   min-width: 0;
   min-height: 40px;
   border: none;
 }
 
 .downloadsPanelFooterButton:hover {
-  outline: 1px solid hsla(210,4%,10%,.07);
-  background-color: hsla(210,4%,10%,.07);
+  outline: 1px solid var(--arrowpanel-dimmed);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 .downloadsPanelFooterButton:hover:active,
 .downloadsPanelFooterButton[open="true"] {
-  outline: 1px solid hsla(210,4%,10%,.12);
-  background-color: hsla(210,4%,10%,.12);
+  outline: 1px solid var(--arrowpanel-dimmed-further);
+  background-color: var(--arrowpanel-dimmed-further);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 .downloadsPanelFooterButton[default] {
   background-color: #0996f8;
   color: white;
 }
 
--- a/browser/themes/shared/plugin-doorhanger.inc.css
+++ b/browser/themes/shared/plugin-doorhanger.inc.css
@@ -21,18 +21,18 @@
   background-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.png");
   background-repeat: no-repeat;
   width: 16px;
   height: 15px;
   margin-inline-start: 6px;
 }
 
 .click-to-play-plugins-notification-button-container {
-  background-color: hsla(210,4%,10%,.07);
-  border-top: 1px solid hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed);
+  border-top: 1px solid var(--panel-separator-color);
   padding: 10px;
   margin-top: 5px;
 }
 
 .click-to-play-popup-button {
   width: 50%;
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -41,16 +41,19 @@
   --urlbar-dropmarker-hover-region: rect(0px, 22px, 14px, 11px);
   --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
   --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
   --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
   --urlbar-dropmarker-hover-2x-region: rect(0, 44px, 28px, 22px);
   --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
 
   --panel-separator-color: ThreeDLightShadow;
+  --arrowpanel-dimmed: hsla(210,4%,10%,.07);
+  --arrowpanel-dimmed-further: hsla(210,4%,10%,.12);
+  --arrowpanel-dimmed-even-further: hsla(210,4%,10%,.17);
 
   --urlbar-separator-color: ThreeDLightShadow;
 }
 
 @media (-moz-windows-default-theme) {
   :root {
     --panel-separator-color: hsla(210,4%,10%,.14);
   }
@@ -1439,35 +1442,35 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 }
 
 .search-go-container {
   padding: 2px 2px;
 }
 
 #urlbar-search-footer {
   border-top: 1px solid var(--panel-separator-color);
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings {
   -moz-appearance: none;
   -moz-user-focus: ignore;
   color: GrayText;
   margin: 0;
   border: 0;
   padding: 8px 20px;
   background: transparent;
 }
 
 #urlbar-search-settings:hover {
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
 }
 
 #urlbar-search-settings:hover:active {
-  background-color: hsla(210,4%,10%,.12);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 #urlbar-search-splitter {
   min-width: 6px;
   margin-inline-start: -3px;
   border: none;
   background: transparent;
 }
--- a/browser/themes/windows/searchbar.css
+++ b/browser/themes/windows/searchbar.css
@@ -127,17 +127,17 @@
 }
 
 .search-panel-tree {
   border-top: 1px solid var(--panel-separator-color) !important;
 }
 
 .search-panel-header {
   font-weight: normal;
-  background-color: hsla(210,4%,10%,.07);
+  background-color: var(--arrowpanel-dimmed);
   border: none;
   border-top: 1px solid var(--panel-separator-color);
   margin: 0;
   padding: 3px 6px;
   color: GrayText;
 }
 
 .search-panel-header > label {
@@ -288,17 +288,17 @@
 }
 
 .search-setting-button {
   -moz-appearance: none;
   min-height: 32px;
 }
 
 .search-setting-button[selected] {
-  background-color: hsla(210,4%,10%,.15);
+  background-color: var(--arrowpanel-dimmed-further);
 }
 
 .search-setting-button-compact {
   list-style-image: url("chrome://browser/skin/gear.svg#gear");
 }
 
 .search-setting-button-compact[selected] {
   list-style-image: url("chrome://browser/skin/gear.svg#gear-inverted");
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -1,12 +1,20 @@
 dnl This Source Code Form is subject to the terms of the Mozilla Public
 dnl License, v. 2.0. If a copy of the MPL was not distributed with this
 dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+dnl Several autoconf functions AC_REQUIRE AC_PROG_CPP/AC_PROG_CXXCPP,
+dnl meaning they are called even when we don't call them explicitly.
+dnl However, theses checks are not necessary and python configure sets
+dnl the corresponding variables already, so just skip those tests
+dnl entirely.
+define([AC_PROG_CPP],[])
+define([AC_PROG_CXXCPP],[])
+
 AC_DEFUN([MOZ_TOOL_VARIABLES],
 [
 GNU_AS=
 GNU_LD=
 
 GNU_CC=
 GNU_CXX=
 if test "$CC_TYPE" = "gcc"; then
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -749,16 +749,33 @@ def compiler(language, host_or_target, c
 
     def compiler_error():
         raise FatalCheckError('Failed compiling a simple %s source with %s'
                               % (language, what))
 
     valid_compiler.try_compile(check_msg='%s works' % what,
                                onerror=compiler_error)
 
+
+    # Set CPP/CXXCPP for both the build system and old-configure. We don't
+    # need to check this works for preprocessing, because we already relied
+    # on $CC -E/$CXX -E doing preprocessing work to validate the compiler
+    # in the first place.
+    if host_or_target == target:
+        pp_var = {
+            'C': 'CPP',
+            'C++': 'CXXCPP',
+        }[language]
+
+        preprocessor = depends_if(valid_compiler)(
+                lambda x: list(x.wrapper) + [x.compiler, '-E'] + list(x.flags))
+
+        set_config(pp_var, preprocessor)
+        add_old_configure_assignment(pp_var, preprocessor)
+
     return valid_compiler
 
 
 c_compiler = compiler('C', target)
 cxx_compiler = compiler('C++', target, c_compiler=c_compiler)
 host_c_compiler = compiler('C', host, other_compiler=c_compiler)
 host_cxx_compiler = compiler('C++', host, c_compiler=host_c_compiler,
                              other_compiler=cxx_compiler,
--- a/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js
+++ b/devtools/client/webconsole/test/browser_webconsole_strict_mode_errors.js
@@ -16,17 +16,17 @@ add_task(function* () {
   yield loadTab("data:text/html;charset=utf8,<script>'use strict';var arguments;</script>");
 
   let hud = yield openConsole();
 
   yield waitForMessages({
     webconsole: hud,
     messages: [
       {
-        text: "SyntaxError: redefining arguments is deprecated",
+        text: "SyntaxError: 'arguments' can't be defined or assigned to in strict mode code",
         category: CATEGORY_JS,
         severity: SEVERITY_ERROR,
       },
     ],
   });
 
   if (!Services.appinfo.browserTabsRemoteAutostart) {
     expectUncaughtException();
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -122,16 +122,32 @@ KeyframeEffect::SetTarget(const Nullable
     }
   } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
     // New target is null, so fall back to distribute spacing.
     KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
   }
 }
 
 void
+KeyframeEffect::SetIterationComposite(
+  const IterationCompositeOperation& aIterationComposite)
+{
+  if (mEffectOptions.mIterationComposite == aIterationComposite) {
+    return;
+  }
+
+  if (mAnimation && mAnimation->IsRelevant()) {
+    nsNodeUtils::AnimationChanged(mAnimation);
+  }
+
+  mEffectOptions.mIterationComposite = aIterationComposite;
+  RequestRestyle(EffectCompositor::RestyleType::Layer);
+}
+
+void
 KeyframeEffect::SetSpacing(JSContext* aCx,
                            const nsAString& aSpacing,
                            ErrorResult& aRv)
 {
   SpacingMode spacingMode = SpacingMode::distribute;
   nsCSSPropertyID pacedProperty = eCSSProperty_UNKNOWN;
   nsAutoString invalidPacedProperty;
   KeyframeEffectParams::ParseSpacing(aSpacing,
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -62,14 +62,16 @@ public:
   // This method calls GetTargetStyleContext which is not safe to use when
   // we are in the middle of updating style. If we need to use this when
   // updating style, we should pass the nsStyleContext into this method and use
   // that to update the properties rather than calling
   // GetStyleContextForElement.
   void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
 
   void SetSpacing(JSContext* aCx, const nsAString& aSpacing, ErrorResult& aRv);
+  void SetIterationComposite(
+    const IterationCompositeOperation& aIterationComposite);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/KeyframeEffectParams.h
+++ b/dom/animation/KeyframeEffectParams.h
@@ -4,16 +4,21 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_KeyframeEffectParams_h
 #define mozilla_KeyframeEffectParams_h
 
 #include "nsCSSProps.h"
 #include "nsString.h"
+// X11 has a #define for None
+#ifdef None
+#undef None
+#endif
+#include "mozilla/dom/KeyframeEffectBinding.h" // IterationCompositeOperation
 
 namespace mozilla {
 
 class ErrorResult;
 
 enum class SpacingMode
 {
   distribute,
@@ -46,17 +51,18 @@ struct KeyframeEffectParams
    * @param [out] aRv The error result.
    */
   static void ParseSpacing(const nsAString& aSpacing,
                            SpacingMode& aSpacingMode,
                            nsCSSPropertyID& aPacedProperty,
                            nsAString& aInvalidPacedProperty,
                            ErrorResult& aRv);
 
-  // FIXME: Bug 1216843: Add IterationCompositeOperations and
-  //        Bug 1216844: Add CompositeOperation
+  dom::IterationCompositeOperation mIterationComposite =
+    dom::IterationCompositeOperation::Replace;
+  // FIXME: Bug 1216844: Add CompositeOperation
   SpacingMode mSpacingMode = SpacingMode::distribute;
   nsCSSPropertyID mPacedProperty = eCSSProperty_UNKNOWN;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_KeyframeEffectParams_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -72,17 +72,17 @@ KeyframeEffectReadOnly::WrapObject(JSCon
                                    JS::Handle<JSObject*> aGivenProto)
 {
   return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
 }
 
 IterationCompositeOperation
 KeyframeEffectReadOnly::IterationComposite() const
 {
-  return IterationCompositeOperation::Replace;
+  return mEffectOptions.mIterationComposite;
 }
 
 CompositeOperation
 KeyframeEffectReadOnly::Composite() const
 {
   return CompositeOperation::Replace;
 }
 
@@ -106,35 +106,32 @@ KeyframeEffectReadOnly::NotifyAnimationT
   // animation cascade for this element whenever that changes.
   bool inEffect = IsInEffect();
   if (inEffect != mInEffectOnLastAnimationTimingUpdate) {
     MarkCascadeNeedsUpdate();
     mInEffectOnLastAnimationTimingUpdate = inEffect;
   }
 
   // Request restyle if necessary.
-  //
-  // Bug 1216843: When we implement iteration composite modes, we need to
-  // also detect if the current iteration has changed.
-  if (mAnimation &&
-      !mProperties.IsEmpty() &&
-      GetComputedTiming().mProgress != mProgressOnLastCompose) {
+  if (mAnimation && !mProperties.IsEmpty() && HasComputedTimingChanged()) {
     EffectCompositor::RestyleType restyleType =
       CanThrottle() ?
       EffectCompositor::RestyleType::Throttled :
       EffectCompositor::RestyleType::Standard;
     RequestRestyle(restyleType);
   }
 
   // If we're no longer "in effect", our ComposeStyle method will never be
-  // called and we will never have a chance to update mProgressOnLastCompose.
-  // We clear mProgressOnLastCompose here to ensure that if we later become
-  // "in effect" we will request a restyle (above).
+  // called and we will never have a chance to update mProgressOnLastCompose
+  // and mCurrentIterationOnLastCompose.
+  // We clear them here to ensure that if we later become "in effect" we will
+  // request a restyle (above).
   if (!inEffect) {
      mProgressOnLastCompose.SetNull();
+     mCurrentIterationOnLastCompose = 0;
   }
 }
 
 static bool
 KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe>& aLhs,
                                       const nsTArray<Keyframe>& aRhs)
 {
   if (aLhs.Length() != aRhs.Length()) {
@@ -307,16 +304,17 @@ KeyframeEffectReadOnly::UpdateProperties
 }
 
 void
 KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                                      nsCSSPropertyIDSet& aSetProperties)
 {
   ComputedTiming computedTiming = GetComputedTiming();
   mProgressOnLastCompose = computedTiming.mProgress;
+  mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mProgress.IsNull()) {
     return;
   }
 
   for (size_t propIdx = 0, propEnd = mProperties.Length();
@@ -368,45 +366,75 @@ KeyframeEffectReadOnly::ComposeStyle(Ref
                  prop.mSegments.Length(),
                "out of array bounds");
 
     if (!aStyleRule) {
       // Allocate the style rule now that we know we have animation data.
       aStyleRule = new AnimValuesStyleRule();
     }
 
+    StyleAnimationValue fromValue = segment->mFromValue;
+    StyleAnimationValue toValue = segment->mToValue;
+    // Iteration composition for accumulate
+    if (mEffectOptions.mIterationComposite ==
+          IterationCompositeOperation::Accumulate &&
+        computedTiming.mCurrentIteration > 0) {
+      const AnimationPropertySegment& lastSegment =
+        prop.mSegments.LastElement();
+      // FIXME: Bug 1293492: Add a utility function to calculate both of
+      // below StyleAnimationValues.
+      DebugOnly<bool> accumulateResult =
+        StyleAnimationValue::Accumulate(prop.mProperty,
+                                        fromValue,
+                                        lastSegment.mToValue,
+                                        computedTiming.mCurrentIteration);
+      // We can't check the accumulation result in case of filter property.
+      // That's because some filter property can't accumulate,
+      // e.g. 'contrast(2) brightness(2)' onto 'brightness(1) contrast(1)'
+      // because of mismatch of the order.
+      MOZ_ASSERT(accumulateResult || prop.mProperty == eCSSProperty_filter,
+                 "could not accumulate value");
+      accumulateResult =
+        StyleAnimationValue::Accumulate(prop.mProperty,
+                                        toValue,
+                                        lastSegment.mToValue,
+                                        computedTiming.mCurrentIteration);
+      MOZ_ASSERT(accumulateResult || prop.mProperty == eCSSProperty_filter,
+                 "could not accumulate value");
+    }
+
     // Special handling for zero-length segments
     if (segment->mToKey == segment->mFromKey) {
       if (computedTiming.mProgress.Value() < 0) {
-        aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
+        aStyleRule->AddValue(prop.mProperty, Move(fromValue));
       } else {
-        aStyleRule->AddValue(prop.mProperty, segment->mToValue);
+        aStyleRule->AddValue(prop.mProperty, Move(toValue));
       }
       continue;
     }
 
     double positionInSegment =
       (computedTiming.mProgress.Value() - segment->mFromKey) /
       (segment->mToKey - segment->mFromKey);
     double valuePosition =
       ComputedTimingFunction::GetPortion(segment->mTimingFunction,
                                          positionInSegment,
                                          computedTiming.mBeforeFlag);
 
     MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
     StyleAnimationValue val;
     if (StyleAnimationValue::Interpolate(prop.mProperty,
-                                         segment->mFromValue,
-                                         segment->mToValue,
+                                         fromValue,
+                                         toValue,
                                          valuePosition, val)) {
       aStyleRule->AddValue(prop.mProperty, Move(val));
     } else if (valuePosition < 0.5) {
-      aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
+      aStyleRule->AddValue(prop.mProperty, Move(fromValue));
     } else {
-      aStyleRule->AddValue(prop.mProperty, segment->mToValue);
+      aStyleRule->AddValue(prop.mProperty, Move(toValue));
     }
   }
 }
 
 bool
 KeyframeEffectReadOnly::IsRunningOnCompositor() const
 {
   // We consider animation is running on compositor if there is at least
@@ -477,16 +505,17 @@ KeyframeEffectParamsFromUnion(const Opti
   if (!aOptions.IsUnrestrictedDouble()) {
     const KeyframeEffectOptions& options =
       KeyframeEffectOptionsFromUnion(aOptions);
     KeyframeEffectParams::ParseSpacing(options.mSpacing,
                                        result.mSpacingMode,
                                        result.mPacedProperty,
                                        aInvalidPacedProperty,
                                        aRv);
+    result.mIterationComposite = options.mIterationComposite;
   }
   return result;
 }
 
 /* static */ Maybe<OwningAnimationTarget>
 KeyframeEffectReadOnly::ConvertTarget(
   const Nullable<ElementOrCSSPseudoElement>& aTarget)
 {
@@ -1275,10 +1304,25 @@ KeyframeEffectReadOnly::MarkCascadeNeeds
   EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
                                                  mTarget->mPseudoType);
   if (!effectSet) {
     return;
   }
   effectSet->MarkCascadeNeedsUpdate();
 }
 
+bool
+KeyframeEffectReadOnly::HasComputedTimingChanged() const
+{
+  // Typically we don't need to request a restyle if the progress hasn't
+  // changed since the last call to ComposeStyle. The one exception is if the
+  // iteration composite mode is 'accumulate' and the current iteration has
+  // changed, since that will often produce a different result.
+  ComputedTiming computedTiming = GetComputedTiming();
+  return computedTiming.mProgress != mProgressOnLastCompose ||
+         (mEffectOptions.mIterationComposite ==
+            IterationCompositeOperation::Accumulate &&
+         computedTiming.mCurrentIteration !=
+          mCurrentIterationOnLastCompose);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -370,28 +370,37 @@ protected:
   // A set of per-property value arrays, derived from |mKeyframes|.
   nsTArray<AnimationProperty> mProperties;
 
   // The computed progress last time we composed the style rule. This is
   // used to detect when the progress is not changing (e.g. due to a step
   // timing function) so we can avoid unnecessary style updates.
   Nullable<double> mProgressOnLastCompose;
 
+  // The purpose of this value is the same as mProgressOnLastCompose but
+  // this is used to detect when the current iteration is not changing
+  // in the case when iterationComposite is accumulate.
+  uint64_t mCurrentIterationOnLastCompose = 0;
+
   // We need to track when we go to or from being "in effect" since
   // we need to re-evaluate the cascade of animations when that changes.
   bool mInEffectOnLastAnimationTimingUpdate;
 
 private:
   nsChangeHint mCumulativeChangeHint;
 
   nsIFrame* GetAnimationFrame() const;
 
   bool CanThrottle() const;
   bool CanThrottleTransformChanges(nsIFrame& aFrame) const;
 
+  // Returns true if the computedTiming has changed since the last
+  // composition.
+  bool HasComputedTimingChanged() const;
+
   // Returns true unless Gecko limitations prevent performing transform
   // animations for |aFrame|. When returning true, the reason for the
   // limitation is stored in |aOutPerformanceWarning|.
   static bool CanAnimateTransformOnCompositor(
     const nsIFrame* aFrame,
     AnimationPerformanceWarning::Type& aPerformanceWarning);
   static bool IsGeometricProperty(const nsCSSPropertyID aProperty);
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8162,27 +8162,31 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
                                bool aIsWidgetEventSynthesized)
 {
   nsPoint offset;
   nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
   if (!widget)
     return NS_ERROR_FAILURE;
 
   EventMessage msg;
+  WidgetMouseEvent::ExitFrom exitFrom = WidgetMouseEvent::eChild;
   bool contextMenuKey = false;
   if (aType.EqualsLiteral("mousedown")) {
     msg = eMouseDown;
   } else if (aType.EqualsLiteral("mouseup")) {
     msg = eMouseUp;
   } else if (aType.EqualsLiteral("mousemove")) {
     msg = eMouseMove;
   } else if (aType.EqualsLiteral("mouseover")) {
     msg = eMouseEnterIntoWidget;
   } else if (aType.EqualsLiteral("mouseout")) {
     msg = eMouseExitFromWidget;
+  } else if (aType.EqualsLiteral("mousecancel")) {
+    msg = eMouseExitFromWidget;
+    exitFrom = WidgetMouseEvent::eTopLevel;
   } else if (aType.EqualsLiteral("mouselongtap")) {
     msg = eMouseLongTap;
   } else if (aType.EqualsLiteral("contextmenu")) {
     msg = eContextMenu;
     contextMenuKey = (aButton == 0);
   } else if (aType.EqualsLiteral("MozMouseHittest")) {
     msg = eMouseHitTest;
   } else {
@@ -8204,16 +8208,17 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
   event.buttons = aButtons != nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED ?
                   aButtons :
                   msg == eMouseUp ? 0 : GetButtonsFlagForButton(aButton);
   event.pressure = aPressure;
   event.inputSource = aInputSourceArg;
   event.mClickCount = aClickCount;
   event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized;
+  event.mExitFrom = exitFrom;
 
   nsPresContext* presContext = aPresShell->GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
   event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
   event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2576,16 +2576,54 @@ nsJSContext::EnsureStatics()
 
   sIsInitialized = true;
 }
 
 void
 nsJSContext::NotifyDidPaint()
 {
   sDidPaintAfterPreviousICCSlice = true;
+  if (sICCTimer) {
+    static uint32_t sCount = 0;
+    // 16 here is the common value for refresh driver tick frequency.
+    static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
+    if (++sCount % kTicksPerSliceDelay != 0) {
+      // Don't trigger CC slice all the time after paint, but often still.
+      // The key point is to trigger it right after paint, especially when
+      // we're running RefreshDriver constantly.
+      return;
+    }
+
+    sICCTimer->Cancel();
+    ICCTimerFired(nullptr, nullptr);
+    if (sICCTimer) {
+      sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+                                           kICCIntersliceDelay,
+                                           nsITimer::TYPE_REPEATING_SLACK,
+                                           "ICCTimerFired");
+    }
+  } else if (sCCTimer) {
+    static uint32_t sCount = 0;
+    static const uint32_t kTicksPerForgetSkippableDelay =
+      NS_CC_SKIPPABLE_DELAY / 16;
+    if (++sCount % kTicksPerForgetSkippableDelay != 0) {
+      // The comment above about triggering CC slice applies to forget skippable
+      // too.
+      return;
+    }
+
+    sCCTimer->Cancel();
+    CCTimerFired(nullptr, nullptr);
+    if (sCCTimer) {
+      sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+                                          NS_CC_SKIPPABLE_DELAY,
+                                          nsITimer::TYPE_REPEATING_SLACK,
+                                          "CCTimerFired");
+    }
+  }
 }
 
 nsScriptNameSpaceManager*
 mozilla::dom::GetNameSpaceManager()
 {
   if (sDidShutdown)
     return nullptr;
 
--- a/dom/bindings/GenerateCSS2PropertiesWebIDL.py
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -11,17 +11,17 @@ from mozbuild import shellutil
 
 # Generates a line of WebIDL with the given spelling of the property name
 # (whether camelCase, _underscorePrefixed, etc.) and the given array of
 # extended attributes.
 def generateLine(propName, extendedAttrs):
     return "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
                                                  propName)
 def generate(output, idlFilename, preprocessorHeader):
-    cpp = shellutil.split(buildconfig.substs['CPP'])
+    cpp = list(buildconfig.substs['CPP'])
     cpp += shellutil.split(buildconfig.substs['ACDEFINES'])
     cpp.append(preprocessorHeader)
     preprocessed = subprocess.check_output(cpp)
 
     propList = eval(preprocessed)
     props = ""
     for [name, prop, id, flags, pref, proptype] in propList:
         if "CSS_PROPERTY_INTERNAL" in flags:
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1903,16 +1903,18 @@ CanvasRenderingContext2D::ReturnTarget(b
         }
       }
 
       if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
         // With the cairo backend we pushed an extra clip rect which we have to
         // balance out here. See the comment in RestoreClipsAndTransformToTarget.
         mTarget->PopClip();
       }
+
+      mTarget->SetTransform(Matrix());
     }
 
     mBufferProvider->ReturnDrawTarget(mTarget.forget());
   }
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell,
--- a/dom/events/test/pointerevents/mochitest.ini
+++ b/dom/events/test/pointerevents/mochitest.ini
@@ -56,17 +56,16 @@ support-files =
 [test_pointerevent_pointerleave_descendants-manual.html]
   support-files = pointerevent_pointerleave_descendants-manual.html
 [test_pointerevent_pointerleave_does_not_bubble-manual.html]
   support-files = pointerevent_pointerleave_does_not_bubble-manual.html
 [test_pointerevent_pointerleave_mouse-manual.html]
   support-files = pointerevent_pointerleave_mouse-manual.html
 [test_pointerevent_pointerleave_pen-manual.html]
   support-files = pointerevent_pointerleave_pen-manual.html
-  disabled = should be investigated
 [test_pointerevent_pointerleave_touch-manual.html]
   support-files = pointerevent_pointerleave_touch-manual.html
 [test_pointerevent_pointermove-manual.html]
   support-files = pointerevent_pointermove-manual.html
 [test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html]
   support-files = pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html
 [test_pointerevent_pointermove-on-chorded-mouse-button.html]
   support-files = pointerevent_pointermove-on-chorded-mouse-button.html
@@ -75,17 +74,16 @@ support-files =
 [test_pointerevent_pointerout-manual.html]
   support-files = pointerevent_pointerout-manual.html
 [test_pointerevent_pointerout_after_pointercancel_touch-manual.html]
   support-files = pointerevent_pointerout_after_pointercancel_touch-manual.html
 [test_pointerevent_pointerout_after_pointerup_nohover-manual.html]
   support-files = pointerevent_pointerout_after_pointerup_nohover-manual.html
 [test_pointerevent_pointerout_pen-manual.html]
   support-files = pointerevent_pointerout_pen-manual.html
-  disabled = should be investigated
 [test_pointerevent_pointerout_received_once-manual.html]
   support-files = pointerevent_pointerout_received_once-manual.html
 [test_pointerevent_pointerover-manual.html]
   support-files = pointerevent_pointerover-manual.html
 [test_pointerevent_pointertype_mouse-manual.html]
   support-files = pointerevent_pointertype_mouse-manual.html
 [test_pointerevent_pointertype_pen-manual.html]
   support-files = pointerevent_pointertype_pen-manual.html
--- a/dom/events/test/pointerevents/mochitest_support_external.js
+++ b/dom/events/test/pointerevents/mochitest_support_external.js
@@ -37,36 +37,16 @@ if(testObj["status"] != testObj["PASS"])
 function completion_function() {
   console.log("w3c tests have been finished");
   if(!SimpleTest._stopOnLoad) {
     console.log("Finishing Mochitest system");
     SimpleTest.finish();
   }
 }
 
-// Helper function to send PointerEvent with different parameters
-function sendPointerEvent(int_win, elemId, pointerEventType, inputSource, params) {
-  var elem = int_win.document.getElementById(elemId);
-  if(!!elem) {
-    var rect = elem.getBoundingClientRect();
-    var eventObj = {type: pointerEventType, inputSource: inputSource};
-    if(params && "button" in params)
-      eventObj.button = params.button;
-    if(params && "isPrimary" in params)
-      eventObj.isPrimary = params.isPrimary;
-    else if(MouseEvent.MOZ_SOURCE_MOUSE == inputSource)
-      eventObj.isPrimary = true;
-    console.log(elemId, eventObj);
-    var salt = ("pointermove" == pointerEventType) ? 1 : 2;
-    synthesizePointer(elem, rect.width*salt/5, rect.height/2, eventObj, int_win);
-  } else {
-    is(!!elem, true, "Document should have element with id: " + elemId);
-  }
-}
-
 // Helper function to send MouseEvent with different parameters
 function sendMouseEvent(int_win, elemId, mouseEventType, params) {
   var elem = int_win.document.getElementById(elemId);
   if(!!elem) {
     var rect = elem.getBoundingClientRect();
     var eventObj = {type: mouseEventType};
     if(params && "button" in params)
       eventObj.button = params.button;
--- a/dom/events/test/pointerevents/test_pointerevent_capture_mouse-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_capture_mouse-manual.html
@@ -13,22 +13,22 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_capture_mouse-manual.html";
       }
       function executeTest(int_win) {
-        sendPointerEvent(int_win, "btnCapture", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target0",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "btnCapture", "pointerdown", MouseEvent.MOZ_SOURCE_MOUSE, {button:1});
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointerup",   MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
+        sendMouseEvent(int_win, "btnCapture", "mousemove");
+        sendMouseEvent(int_win, "target0",    "mousemove");
+        sendMouseEvent(int_win, "target1",    "mousemove");
+        sendMouseEvent(int_win, "btnCapture", "mousedown", {button:1});
+        sendMouseEvent(int_win, "target1",    "mousemove");
+        sendMouseEvent(int_win, "target1",    "mouseup");
+        sendMouseEvent(int_win, "target1",    "mousemove");
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_capture_suppressing_mouse-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_capture_suppressing_mouse-manual.html
@@ -13,23 +13,23 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_capture_suppressing_mouse-manual.html";
       }
       function executeTest(int_win) {
-        sendPointerEvent(int_win, "target0",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "btnCapture", "pointerdown", MouseEvent.MOZ_SOURCE_MOUSE, {button:1});
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target0",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointerup",   MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1",    "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
+        sendMouseEvent(int_win, "target0",    "mousemove");
+        sendMouseEvent(int_win, "target1",    "mousemove");
+        sendMouseEvent(int_win, "btnCapture", "mousedown", {button:1});
+        sendMouseEvent(int_win, "target1",    "mousemove");
+        sendMouseEvent(int_win, "target0",    "mousemove");
+        sendMouseEvent(int_win, "target1",    "mousemove");
+        sendMouseEvent(int_win, "target1",    "mouseup");
+        sendMouseEvent(int_win, "target1",    "mousemove");
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendants-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_descendants-manual.html
@@ -13,17 +13,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_pointerleave_descendants-manual.html";
       }
       function executeTest(int_win) {
-        sendPointerEvent(int_win, "target0", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "log",     "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
+        sendMouseEvent(int_win, "target0", "mousemove");
+        sendMouseEvent(int_win, "log",     "mousemove");
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_pointerleave_pen-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_pointerleave_pen-manual.html
@@ -13,18 +13,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_pointerleave_pen-manual.html";
       }
       function executeTest(int_win) {
-        sendMouseEvent(int_win, "target0", "mousedown",  {inputSource:MouseEvent.MOZ_SOURCE_PEN});
-        sendMouseEvent(int_win, "target0", "mouseup",    {inputSource:MouseEvent.MOZ_SOURCE_PEN});
-        sendMouseEvent(int_win, "target0", "mouseleave", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mousedown",   {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mouseup",     {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mousecancel", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_pointerout_pen-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_pointerout_pen-manual.html
@@ -13,18 +13,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_pointerout_pen-manual.html";
       }
       function executeTest(int_win) {
-        sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
-        sendMouseEvent(int_win, "target0", "mouseup",   {inputSource:MouseEvent.MOZ_SOURCE_PEN});
-        sendMouseEvent(int_win, "target0", "mouseout",  {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mousedown",   {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mouseup",     {inputSource:MouseEvent.MOZ_SOURCE_PEN});
+        sendMouseEvent(int_win, "target0", "mousecancel", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html
@@ -13,17 +13,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_releasepointercapture_onpointerup_mouse-manual.html";
       }
       function executeTest(int_win) {
-        sendPointerEvent(int_win, "btnCapture", "pointerdown", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "btnCapture", "pointerup",   MouseEvent.MOZ_SOURCE_MOUSE);
+        sendMouseEvent(int_win, "btnCapture", "mousedown");
+        sendMouseEvent(int_win, "btnCapture", "mouseup");
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/events/test/pointerevents/test_pointerevent_setpointercapture_inactive_button_mouse-manual.html
+++ b/dom/events/test/pointerevents/test_pointerevent_setpointercapture_inactive_button_mouse-manual.html
@@ -13,18 +13,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="text/javascript">
       SimpleTest.waitForExplicitFinish();
       function startTest() {
         var iframe = document.getElementById("testFrame");
         iframe.src = "pointerevent_setpointercapture_inactive_button_mouse-manual.html";
       }
       function executeTest(int_win) {
-        sendPointerEvent(int_win, "target1", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target0", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target1", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
+        sendMouseEvent(int_win, "target1", "mousemove");
+        sendMouseEvent(int_win, "target0", "mousemove");
+        sendMouseEvent(int_win, "target1", "mousemove");
       }
     </script>
   </head>
   <body>
     <iframe id="testFrame" height="800" width="1000"></iframe>
   </body>
 </html>
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1671,20 +1671,19 @@ nsGenericHTMLElement::GetURIAttr(nsIAtom
                                             attr->GetStringValue(),
                                             OwnerDoc(), baseURI);
   return true;
 }
 
 /* static */ bool
 nsGenericHTMLElement::IsScrollGrabAllowed(JSContext*, JSObject*)
 {
-  // Only allow scroll grabbing in chrome and certified apps.
+  // Only allow scroll grabbing in chrome
   nsIPrincipal* prin = nsContentUtils::SubjectPrincipal();
-  return nsContentUtils::IsSystemPrincipal(prin) ||
-    prin->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
+  return nsContentUtils::IsSystemPrincipal(prin);
 }
 
 nsresult
 nsGenericHTMLElement::GetURIListAttr(nsIAtom* aAttr, nsAString& aResult)
 {
   aResult.Truncate();
 
   nsAutoString value;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -275,18 +275,18 @@ interface nsIDOMWindowUtils : nsISupport
   const long MODIFIER_FNLOCK     = 0x0080;
   const long MODIFIER_NUMLOCK    = 0x0100;
   const long MODIFIER_SCROLLLOCK = 0x0200;
   const long MODIFIER_SYMBOL     = 0x0400;
   const long MODIFIER_SYMBOLLOCK = 0x0800;
   const long MODIFIER_OS         = 0x1000;
 
   /** Synthesize a mouse event. The event types supported are:
-   *    mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu,
-   *    MozMouseHittest
+   *    mousedown, mouseup, mousemove, mouseover, mouseout, mousecancel,
+   *    contextmenu, MozMouseHittest
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Note that additional events may be fired as a result of this call. For
    * instance, typically a click event will be fired as a result of a
    * mousedown and mouseup in sequence.
    *
    * Normally at this level of events, the mouseover and mouseout events are
@@ -296,16 +296,21 @@ interface nsIDOMWindowUtils : nsISupport
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
    * The event is dispatched via the toplevel window, so it could go to any
    * window under the toplevel window, in some cases it could never reach this
    * window at all.
    *
+   * NOTE: mousecancel is used to represent the vanishing of an input device
+   * such as a pen leaving its digitizer by synthesizing a WidgetMouseEvent,
+   * whose mMessage is eMouseExitFromWidget and mExitFrom is
+   * WidgetMouseEvent::eTopLevel.
+   *
    * @param aType event type
    * @param aX x offset in CSS pixels
    * @param aY y offset in CSS pixels
    * @param aButton button to synthesize
    * @param aClickCount number of clicks that have been performed
    * @param aModifiers modifiers pressed, using constants defined as MODIFIER_*
    * @param aIgnoreRootScrollFrame whether the event should ignore viewport bounds
    *                           during dispatch
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -64,17 +64,16 @@ partial interface KeyframeEffectReadOnly
 };
 
 [Func="nsDocument::IsWebAnimationsEnabled",
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   inherit attribute (Element or CSSPseudoElement)? target;
-  // Bug 1216843 - implement animation composition
-  // inherit attribute IterationCompositeOperation iterationComposite;
+  inherit attribute IterationCompositeOperation    iterationComposite;
   // Bug 1216844 - implement additive animation
   // inherit attribute CompositeOperation          composite;
   [SetterThrows]
   inherit attribute DOMString                   spacing;
   [Throws]
   void setKeyframes (object? keyframes);
 };
--- a/dom/webidl/XMLHttpRequest.webidl
+++ b/dom/webidl/XMLHttpRequest.webidl
@@ -68,17 +68,17 @@ interface XMLHttpRequest : XMLHttpReques
 
   readonly attribute unsigned short readyState;
 
   // request
   [Throws]
   void open(ByteString method, DOMString url);
   [Throws]
   void open(ByteString method, DOMString url, boolean async,
-            optional DOMString? user, optional DOMString? password);
+            optional DOMString? user=null, optional DOMString? password=null);
   [Throws]
   void setRequestHeader(ByteString header, ByteString value);
 
   [SetterThrows]
   attribute unsigned long timeout;
 
   [SetterThrows]
   attribute boolean withCredentials;
--- a/dom/xhr/XMLHttpRequest.h
+++ b/dom/xhr/XMLHttpRequest.h
@@ -48,19 +48,18 @@ public:
 
   virtual uint16_t
   ReadyState() const = 0;
 
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl, ErrorResult& aRv) = 0;
 
   virtual void
-  Open(const nsACString& aMethod, const nsAString& aUrl,
-       bool aAsync, const Optional<nsAString>& aUser,
-       const Optional<nsAString>& aPassword, ErrorResult& aRv) = 0;
+  Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
+       const nsAString& aUser, const nsAString& aPassword, ErrorResult& aRv) = 0;
 
   virtual void
   SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
                    ErrorResult& aRv) = 0;
 
   virtual uint32_t
   Timeout() const = 0;
 
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -1395,50 +1395,54 @@ XMLHttpRequestMainThread::Open(const nsA
   Optional<nsAString> username;
   if (optional_argc > 1) {
     username = &aUsername;
   }
   Optional<nsAString> password;
   if (optional_argc > 2) {
     password = &aPassword;
   }
-  return OpenInternal(aMethod, aUrl, async, username, password);
+  return Open(aMethod, aUrl, async, username, password);
 }
 
 // This case is hit when the async parameter is outright omitted, which
 // should set it to true (and the username and password to null).
 void
 XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsAString& aUrl,
                                ErrorResult& aRv)
 {
-  aRv = OpenInternal(aMethod, NS_ConvertUTF16toUTF8(aUrl), Optional<bool>(true),
-                     Optional<nsAString>(), Optional<nsAString>());
+  aRv = Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), Optional<bool>(true),
+             Optional<nsAString>(), Optional<nsAString>());
 }
 
 // This case is hit when the async parameter is specified, even if the
 // JS value was "undefined" (which due to legacy reasons should be
 // treated as true, which is how it will already be passed in here).
 void
 XMLHttpRequestMainThread::Open(const nsACString& aMethod,
                                const nsAString& aUrl,
                                bool aAsync,
-                               const Optional<nsAString>& aUsername,
-                               const Optional<nsAString>& aPassword,
+                               const nsAString& aUsername,
+                               const nsAString& aPassword,
                                ErrorResult& aRv)
 {
-  aRv = OpenInternal(aMethod, NS_ConvertUTF16toUTF8(aUrl),
-                     Optional<bool>(aAsync), aUsername, aPassword);
+  Optional<nsAString> username;
+  username = &aUsername;
+  Optional<nsAString> password;
+  password = &aPassword;
+  aRv = Open(aMethod, NS_ConvertUTF16toUTF8(aUrl),
+             Optional<bool>(aAsync), username, password);
 }
 
 nsresult
-XMLHttpRequestMainThread::OpenInternal(const nsACString& aMethod,
-                                       const nsACString& aUrl,
-                                       const Optional<bool>& aAsync,
-                                       const Optional<nsAString>& aUsername,
-                                       const Optional<nsAString>& aPassword)
+XMLHttpRequestMainThread::Open(const nsACString& aMethod,
+                               const nsACString& aUrl,
+                               const Optional<bool>& aAsync,
+                               const Optional<nsAString>& aUsername,
+                               const Optional<nsAString>& aPassword)
 {
   bool async = aAsync.WasPassed() ? aAsync.Value() : true;
 
   // Gecko-specific
   if (!async && !DontWarnAboutSyncXHR() && GetOwner() &&
       GetOwner()->GetExtantDoc()) {
     GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
   }
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -244,20 +244,26 @@ public:
                          nsACString& aUploadContentType);
 
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl,
        ErrorResult& aRv) override;
 
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
-       const Optional<nsAString>& aUser,
-       const Optional<nsAString>& aPassword,
+       const nsAString& aUsername, const nsAString& aPassword,
        ErrorResult& aRv) override;
 
+  nsresult
+  Open(const nsACString& aMethod,
+       const nsACString& aUrl,
+       const Optional<bool>& aAsync,
+       const Optional<nsAString>& aUsername,
+       const Optional<nsAString>& aPassword);
+
   virtual void
   SetRequestHeader(const nsACString& aName, const nsACString& aValue,
                    ErrorResult& aRv) override
   {
     aRv = SetRequestHeader(aName, aValue);
   }
 
   virtual uint32_t
@@ -586,22 +592,16 @@ protected:
   void OnBodyParseEnd();
   void ChangeStateToDone();
 
   void StartProgressEventTimer();
   void StopProgressEventTimer();
 
   nsresult OnRedirectVerifyCallback(nsresult result);
 
-  nsresult OpenInternal(const nsACString& aMethod,
-                        const nsACString& aUrl,
-                        const Optional<bool>& aAsync,
-                        const Optional<nsAString>& aUsername,
-                        const Optional<nsAString>& aPassword);
-
   already_AddRefed<nsXMLHttpRequestXPCOMifier> EnsureXPCOMifier();
 
   nsCOMPtr<nsISupports> mContext;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIChannel> mChannel;
   nsCString mRequestMethod;
   nsCOMPtr<nsIURI> mRequestURL;
   nsCOMPtr<nsIDocument> mResponseXML;
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -1421,26 +1421,25 @@ OpenRunnable::MainThreadRunInternal()
   if (mTimeout) {
     rv = mProxy->mXHR->SetTimeout(mTimeout);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   MOZ_ASSERT(!mProxy->mInOpen);
   mProxy->mInOpen = true;
 
-  ErrorResult rv2;
-  mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2);
+  rv = mProxy->mXHR->Open(mMethod, NS_ConvertUTF16toUTF8(mURL),
+                          Optional<bool>(true), mUser, mPassword);
 
   MOZ_ASSERT(mProxy->mInOpen);
   mProxy->mInOpen = false;
 
-  if (rv2.Failed()) {
-    return rv2.StealNSResult();
-  }
-
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ErrorResult rv2;
   mProxy->mXHR->SetResponseType(mResponseType, rv2);
   if (rv2.Failed()) {
     return rv2.StealNSResult();
   }
 
   return NS_OK;
 }
 
@@ -1844,19 +1843,21 @@ XMLHttpRequestWorker::Notify(Status aSta
     mCanceled = true;
     ReleaseProxy(WorkerIsGoingAway);
   }
 
   return true;
 }
 
 void
-XMLHttpRequestWorker::Open(const nsACString& aMethod, const nsAString& aUrl,
-                           bool aAsync, const Optional<nsAString>& aUser,
-                           const Optional<nsAString>& aPassword, ErrorResult& aRv)
+XMLHttpRequestWorker::Open(const nsACString& aMethod,
+                           const nsAString& aUrl, bool aAsync,
+                           const Optional<nsAString>& aUser,
+                           const Optional<nsAString>& aPassword,
+                           ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     aRv.ThrowUncatchableException();
     return;
   }
 
--- a/dom/xhr/XMLHttpRequestWorker.h
+++ b/dom/xhr/XMLHttpRequestWorker.h
@@ -92,18 +92,30 @@ public:
        ErrorResult& aRv) override
   {
     Open(aMethod, aUrl, true, Optional<nsAString>(),
          Optional<nsAString>(), aRv);
   }
 
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
-       const Optional<nsAString>& aUser, const Optional<nsAString>& aPassword,
-       ErrorResult& aRv) override;
+       const nsAString& aUsername, const nsAString& aPassword,
+       ErrorResult& aRv) override
+  {
+    Optional<nsAString> username;
+    username = &aUsername;
+    Optional<nsAString> password;
+    password = &aPassword;
+    Open(aMethod, aUrl, aAsync, username, password, aRv);
+  }
+
+  void
+  Open(const nsACString& aMethod, const nsAString& aUrl,
+       bool aAsync, const Optional<nsAString>& aUser,
+       const Optional<nsAString>& aPassword, ErrorResult& aRv);
 
   virtual void
   SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
                    ErrorResult& aRv) override;
 
   virtual uint32_t
   Timeout() const override
   {
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -11,16 +11,17 @@
 #include "LayerManagerComposite.h"      // for LayerManagerComposite, etc
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "gfxPoint.h"                   // for gfxPoint, gfxSize
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
 #include "mozilla/WidgetUtils.h"        // for ComputeTransformForRotation
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode
+#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for RoundedToInt, PointTyped
 #include "mozilla/gfx/Rect.h"           // for RoundedToInt, RectTyped
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/layers/APZUtils.h"    // for CompleteAsyncTransform
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
 #include "mozilla/layers/CompositorThread.h"
@@ -548,28 +549,55 @@ AsyncCompositionManager::AlignFixedAndSt
         //       all of the translation correctly. In such a case,
         //       |a[Previous|Current]TransformForRoot| would need to be adjusted
         //       to reflect only the unconsumed part of the translation.
         return translationConsumed ? TraversalFlag::Skip : TraversalFlag::Continue;
       });
 }
 
 static void
-SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
-            StyleAnimationValue& aEnd, Animatable* aValue, Layer* aLayer)
+SampleValue(float aPortion, Animation& aAnimation,
+            const StyleAnimationValue& aStart, const StyleAnimationValue& aEnd,
+            const StyleAnimationValue& aLastValue, uint64_t aCurrentIteration,
+            Animatable* aValue, Layer* aLayer)
 {
-  StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
                aStart.GetUnit() == StyleAnimationValue::eUnit_None ||
                aEnd.GetUnit() == StyleAnimationValue::eUnit_None,
                "Must have same unit");
+
+  StyleAnimationValue startValue = aStart;
+  StyleAnimationValue endValue = aEnd;
+  // Iteration composition for accumulate
+  if (static_cast<dom::IterationCompositeOperation>
+        (aAnimation.iterationComposite()) ==
+          dom::IterationCompositeOperation::Accumulate &&
+      aCurrentIteration > 0) {
+    // FIXME: Bug 1293492: Add a utility function to calculate both of
+    // below StyleAnimationValues.
+    DebugOnly<bool> accumulateResult =
+      StyleAnimationValue::Accumulate(aAnimation.property(),
+                                      startValue,
+                                      aLastValue,
+                                      aCurrentIteration);
+    MOZ_ASSERT(accumulateResult, "could not accumulate value");
+    accumulateResult =
+      StyleAnimationValue::Accumulate(aAnimation.property(),
+                                      endValue,
+                                      aLastValue,
+                                      aCurrentIteration);
+    MOZ_ASSERT(accumulateResult, "could not accumulate value");
+  }
+
+  StyleAnimationValue interpolatedValue;
   // This should never fail because we only pass transform and opacity values
   // to the compositor and they should never fail to interpolate.
   DebugOnly<bool> uncomputeResult =
-    StyleAnimationValue::Interpolate(aAnimation.property(), aStart, aEnd,
+    StyleAnimationValue::Interpolate(aAnimation.property(),
+                                     startValue, endValue,
                                      aPortion, interpolatedValue);
   MOZ_ASSERT(uncomputeResult, "could not uncompute value");
 
   if (aAnimation.property() == eCSSProperty_opacity) {
     *aValue = interpolatedValue.GetFloatValue();
     return;
   }
 
@@ -678,18 +706,22 @@ SampleAnimations(Layer* aLayer, TimeStam
 
           double portion =
             ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex],
                                                positionInSegment,
                                            computedTiming.mBeforeFlag);
 
           // interpolate the property
           Animatable interpolatedValue;
-          SampleValue(portion, animation, animData.mStartValues[segmentIndex],
-                      animData.mEndValues[segmentIndex], &interpolatedValue, layer);
+          SampleValue(portion, animation,
+                      animData.mStartValues[segmentIndex],
+                      animData.mEndValues[segmentIndex],
+                      animData.mEndValues.LastElement(),
+                      computedTiming.mCurrentIteration,
+                      &interpolatedValue, layer);
           LayerComposite* layerComposite = layer->AsLayerComposite();
           switch (animation.property()) {
           case eCSSProperty_opacity:
           {
             layerComposite->SetShadowOpacity(interpolatedValue.get_float());
             layerComposite->SetShadowOpacitySetByAnimation(true);
             break;
           }
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -199,16 +199,17 @@ struct Animation {
   float iterationStart;
   // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants.
   uint8_t direction;
   nsCSSPropertyID property;
   AnimationData data;
   float playbackRate;
   // This is used in the transformed progress calculation.
   TimingFunction easingFunction;
+  uint8_t iterationComposite;
 };
 
 // Change a layer's attributes
 struct CommonLayerAttributes {
   IntRect layerBounds;
   LayerIntRegion visibleRegion;
   EventRegions eventRegions;
   TransformMatrix transform;
--- a/js/public/HashTable.h
+++ b/js/public/HashTable.h
@@ -30,16 +30,26 @@ template <class> struct DefaultHasher;
 template <class, class> class HashMapEntry;
 namespace detail {
     template <class T> class HashTableEntry;
     template <class T, class HashPolicy, class AllocPolicy> class HashTable;
 } // namespace detail
 
 /*****************************************************************************/
 
+// The "generation" of a hash table is an opaque value indicating the state of
+// modification of the hash table through its lifetime.  If the generation of
+// a hash table compares equal at times T1 and T2, then lookups in the hash
+// table, pointers to (or into) hash table entries, etc. at time T1 are valid
+// at time T2.  If the generation compares unequal, these computations are all
+// invalid and must be performed again to be used.
+//
+// Generations are meaningfully comparable only with respect to a single hash
+// table.  It's always nonsensical to compare the generation of distinct hash
+// tables H1 and H2.
 using Generation = mozilla::Opaque<uint64_t>;
 
 // A JS-friendly, STL-like container providing a hash-based map from keys to
 // values. In particular, HashMap calls constructors and destructors of all
 // objects added so non-PODs may be used safely.
 //
 // Key/Value requirements:
 //  - movable, destructible, assignable
@@ -205,18 +215,16 @@ class HashMap
     // guarantee that |impl| is the first field in HashMap.
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return impl.sizeOfExcludingThis(mallocSizeOf);
     }
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return mallocSizeOf(this) + impl.sizeOfExcludingThis(mallocSizeOf);
     }
 
-    // If |generation()| is the same before and after a HashMap operation,
-    // pointers into the table remain valid.
     Generation generation() const {
         return impl.generation();
     }
 
     /************************************************** Shorthand operations */
 
     bool has(const Lookup& l) const {
         return impl.lookup(l).found();
@@ -446,18 +454,16 @@ class HashSet
     // guarantee that |impl| is the first field in HashSet.
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return impl.sizeOfExcludingThis(mallocSizeOf);
     }
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return mallocSizeOf(this) + impl.sizeOfExcludingThis(mallocSizeOf);
     }
 
-    // If |generation()| is the same before and after a HashSet operation,
-    // pointers into the table remain valid.
     Generation generation() const {
         return impl.generation();
     }
 
     /************************************************** Shorthand operations */
 
     bool has(const Lookup& l) const {
         return impl.lookup(l).found();
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -214,16 +214,24 @@ class JS_FRIEND_API(BaseProxyHandler)
     virtual bool finalizeInBackground(Value priv) const {
         /*
          * Called on creation of a proxy to determine whether its finalize
          * method can be finalized on the background thread.
          */
         return true;
     }
 
+    virtual bool canNurseryAllocate() const {
+        /*
+         * Nursery allocation is allowed if and only if it is safe to not
+         * run |finalize| when the ProxyObject dies.
+         */
+        return false;
+    }
+
     /* Policy enforcement methods.
      *
      * enter() allows the policy to specify whether the caller may perform |act|
      * on the proxy's |id| property. In the case when |act| is CALL, |id| is
      * generally JSID_VOID.
      *
      * The |act| parameter to enter() specifies the action being performed.
      * If |bp| is false, the method suggests that the caller throw (though it
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1716,16 +1716,19 @@ JS_PUBLIC_API(void) HeapValuePostBarrier
 
 template <>
 struct GCPolicy<JS::Value>
 {
     static Value initial() { return UndefinedValue(); }
     static void trace(JSTracer* trc, Value* v, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, v, name);
     }
+    static bool isTenured(const Value& thing) {
+        return !thing.isGCThing() || !IsInsideNursery(thing.toGCThing());
+    }
 };
 
 } // namespace JS
 
 namespace js {
 
 template <>
 struct BarrierMethods<JS::Value>
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -6961,30 +6961,25 @@ CheckStatement(FunctionValidator& f, Par
 static bool
 ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line)
 {
     TokenStream& tokenStream = m.tokenStream();
 
     tokenStream.consumeKnownToken(TOK_FUNCTION, TokenStream::Operand);
     *line = tokenStream.srcCoords.lineNum(tokenStream.currentToken().pos.end);
 
-    RootedPropertyName name(m.cx());
-
     TokenKind tk;
     if (!tokenStream.getToken(&tk, TokenStream::Operand))
         return false;
-    if (tk == TOK_NAME) {
-        name = tokenStream.currentName();
-    } else if (tk == TOK_YIELD) {
-        if (!m.parser().checkYieldNameValidity())
-            return false;
-        name = m.cx()->names().yield;
-    } else {
+    if (tk != TOK_NAME && tk != TOK_YIELD)
         return false;  // The regular parser will throw a SyntaxError, no need to m.fail.
-    }
+
+    RootedPropertyName name(m.cx(), m.parser().bindingIdentifier(YieldIsName));
+    if (!name)
+        return false;
 
     ParseNode* fn = m.parser().handler.newFunctionDefinition();
     if (!fn)
         return false;
 
     RootedFunction& fun = m.dummyFunction();
     fun->setAtom(name);
     fun->setArgCount(0);
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -226,17 +226,16 @@ CodeSegment::create(JSContext* cx,
     uint8_t* codeBase = cs->base();
 
     cs->functionCodeLength_ = linkData.functionCodeLength;
     cs->codeLength_ = bytecode.length();
     cs->globalDataLength_ = linkData.globalDataLength;
     cs->interruptCode_ = codeBase + linkData.interruptOffset;
     cs->outOfBoundsCode_ = codeBase + linkData.outOfBoundsOffset;
     cs->unalignedAccessCode_ = codeBase + linkData.unalignedAccessOffset;
-    cs->badIndirectCallCode_ = codeBase + linkData.badIndirectCallOffset;
 
     {
         JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
         AutoFlushICache afc("CodeSegment::create");
         AutoFlushICache::setRange(uintptr_t(codeBase), cs->codeLength());
 
         memcpy(codeBase, bytecode.begin(), bytecode.length());
         StaticallyLink(*cs, linkData, cx);
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -49,17 +49,16 @@ class CodeSegment
     uint8_t* bytes_;
     uint32_t functionCodeLength_;
     uint32_t codeLength_;
     uint32_t globalDataLength_;
 
     // These are pointers into code for stubs used for asynchronous
     // signal-handler control-flow transfer.
     uint8_t* interruptCode_;
-    uint8_t* badIndirectCallCode_;
     uint8_t* outOfBoundsCode_;
     uint8_t* unalignedAccessCode_;
 
     // The profiling mode may be changed dynamically.
     bool profilingEnabled_;
 
     CodeSegment() { PodZero(this); }
     template <class> friend struct js::MallocProvider;
@@ -79,17 +78,16 @@ class CodeSegment
 
     uint8_t* base() const { return bytes_; }
     uint8_t* globalData() const { return bytes_ + codeLength_; }
     uint32_t codeLength() const { return codeLength_; }
     uint32_t globalDataLength() const { return globalDataLength_; }
     uint32_t totalLength() const { return codeLength_ + globalDataLength_; }
 
     uint8_t* interruptCode() const { return interruptCode_; }
-    uint8_t* badIndirectCallCode() const { return badIndirectCallCode_; }
     uint8_t* outOfBoundsCode() const { return outOfBoundsCode_; }
     uint8_t* unalignedAccessCode() const { return unalignedAccessCode_; }
 
     // The range [0, functionBytes) is a subrange of [0, codeBytes) that
     // contains only function body code, not the stub code. This distinction is
     // used by the async interrupt handler to only interrupt when the pc is in
     // function code which, in turn, simplifies reasoning about how stubs
     // enter/exit.
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -380,22 +380,22 @@ wasm::GenerateFunctionPrologue(MacroAsse
     // Generate table entry thunk:
     masm.haltingAlign(CodeAlignment);
     offsets->tableEntry = masm.currentOffset();
     switch (sigId.kind()) {
       case SigIdDesc::Kind::Global: {
         Register scratch = WasmTableCallScratchReg;
         masm.loadWasmGlobalPtr(sigId.globalDataOffset(), scratch);
         masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, scratch,
-                      JumpTarget::BadIndirectCall);
+                      JumpTarget::IndirectCallBadSig);
         break;
       }
       case SigIdDesc::Kind::Immediate:
         masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, Imm32(sigId.immediate()),
-                      JumpTarget::BadIndirectCall);
+                      JumpTarget::IndirectCallBadSig);
         break;
       case SigIdDesc::Kind::None:
         break;
     }
     offsets->tableProfilingJump = masm.nopPatchableToNearJump().offset();
 
     // Generate normal prologue:
     masm.nopAlign(CodeAlignment);
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -503,17 +503,16 @@ ModuleGenerator::finishCodegen()
     if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, interruptExit))
         return false;
 
     // Fill in LinkData with the offsets of these stubs.
 
     linkData_.interruptOffset = interruptExit.begin;
     linkData_.outOfBoundsOffset = jumpTargets[JumpTarget::OutOfBounds].begin;
     linkData_.unalignedAccessOffset = jumpTargets[JumpTarget::UnalignedAccess].begin;
-    linkData_.badIndirectCallOffset = jumpTargets[JumpTarget::BadIndirectCall].begin;
 
     // Only call convertOutOfRangeBranchesToThunks after all other codegen that may
     // emit new jumps to JumpTargets has finished.
 
     if (!convertOutOfRangeBranchesToThunks())
         return false;
 
     // Now that all thunks have been generated, patch all the thunks.
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -557,16 +557,22 @@ ReadCustomDoubleNaNObject(JSContext* cx,
         return false;
     u64 |= uint32_t(i32);
 
     BitwiseCast(u64, ret);
     return true;
 }
 
 WasmInstanceObject*
+Instance::objectUnbarriered() const
+{
+    return object_.unbarrieredGet();
+}
+
+WasmInstanceObject*
 Instance::object() const
 {
     return object_;
 }
 
 bool
 Instance::callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args)
 {
@@ -838,28 +844,30 @@ Instance::ensureProfilingState(JSContext
         FuncImportTls& import = funcImportTls(fi);
         if (import.obj && import.obj->is<WasmInstanceObject>()) {
             Code& code = import.obj->as<WasmInstanceObject>().instance().code();
             UpdateEntry(code, newProfilingEnabled, &import.code);
         }
     }
 
     for (const SharedTable& table : tables_) {
-        if (!table->isTypedFunction() || !table->initialized())
+        if (!table->isTypedFunction())
             continue;
 
         // This logic will have to be generalized to match the import logic
         // above if wasm can create typed function tables since a single table
         // can contain elements from multiple instances.
         MOZ_ASSERT(metadata().kind == ModuleKind::AsmJS);
 
         void** array = table->internalArray();
         uint32_t length = table->length();
-        for (size_t i = 0; i < length; i++)
-            UpdateEntry(*code_, newProfilingEnabled, &array[i]);
+        for (size_t i = 0; i < length; i++) {
+            if (array[i])
+                UpdateEntry(*code_, newProfilingEnabled, &array[i]);
+        }
     }
 
     return true;
 }
 
 void
 Instance::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                         Metadata::SeenSet* seenMetadata,
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -64,16 +64,20 @@ class Instance
     // Only WasmInstanceObject can call the private trace function.
     friend class js::WasmInstanceObject;
     void tracePrivate(JSTracer* trc);
 
     // Only WasmMemoryObject can call the private onMovingGrow notification.
     friend class js::WasmMemoryObject;
     void onMovingGrow(uint8_t* prevMemoryBase);
 
+    // Called by WasmTableObject to barrier table writes.
+    friend class Table;
+    WasmInstanceObject* objectUnbarriered() const;
+
   public:
     Instance(JSContext* cx,
              HandleWasmInstanceObject object,
              UniqueCode code,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
              Handle<FunctionVector> funcImports,
              const ValVector& globalImports);
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -529,16 +529,18 @@ WasmInstanceObject::create(JSContext* cx
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto<WasmInstanceObject>(cx, proto));
     if (!obj)
         return nullptr;
 
     obj->setReservedSlot(EXPORTS_SLOT, PrivateValue(exports.release()));
     MOZ_ASSERT(obj->isNewborn());
 
+    MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
+
     // Root the Instance via WasmInstanceObject before any possible GC.
     auto* instance = cx->new_<Instance>(cx,
                                         obj,
                                         Move(code),
                                         memory,
                                         Move(tables),
                                         funcImports,
                                         globalImports);
@@ -1195,34 +1197,29 @@ WasmTableObject::getImpl(JSContext* cx, 
     if (indexDbl < 0 || indexDbl >= table.length()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
         return false;
     }
 
     uint32_t index = uint32_t(indexDbl);
     MOZ_ASSERT(double(index) == indexDbl);
 
-    if (!table.initialized()) {
+    ExternalTableElem& elem = table.externalArray()[index];
+    if (!elem.code) {
         args.rval().setNull();
         return true;
     }
 
-    ExternalTableElem& elem = table.externalArray()[index];
     Instance& instance = *elem.tls->instance;
-    const CodeRange* codeRange = instance.code().lookupRange(elem.code);
-
-    // A non-function code range means the bad-indirect-call stub, so a null element.
-    if (!codeRange || !codeRange->isFunction()) {
-        args.rval().setNull();
-        return true;
-    }
+    const CodeRange& codeRange = *instance.code().lookupRange(elem.code);
+    MOZ_ASSERT(codeRange.isFunction());
 
     RootedWasmInstanceObject instanceObj(cx, instance.object());
     RootedFunction fun(cx);
-    if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange->funcDefIndex(), &fun))
+    if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcDefIndex(), &fun))
         return false;
 
     args.rval().setObject(*fun);
     return true;
 }
 
 /* static */ bool
 WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp)
@@ -1253,25 +1250,16 @@ WasmTableObject::setImpl(JSContext* cx, 
     MOZ_ASSERT(double(index) == indexDbl);
 
     RootedFunction value(cx);
     if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
         return false;
     }
 
-    if (!table.initialized()) {
-        if (!value) {
-            args.rval().setUndefined();
-            return true;
-        }
-
-        table.init(ExportedFunctionToInstance(value));
-    }
-
     if (value) {
         RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
         uint32_t funcDefIndex = ExportedFunctionToDefinitionIndex(value);
 
 #ifdef DEBUG
         RootedFunction f(cx);
         MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcDefIndex, &f));
         MOZ_ASSERT(value == f);
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -457,23 +457,16 @@ Module::initSegments(JSContext* cx,
                 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT, "data", "memory");
                 return false;
             }
         }
     } else {
         MOZ_ASSERT(dataSegments_.empty());
     }
 
-    // Ensure all tables are initialized before storing into them.
-
-    for (const SharedTable& table : tables) {
-        if (!table->initialized())
-            table->init(instance);
-    }
-
     // Now that initialization can't fail partway through, write data/elem
     // segments into memories/tables.
 
     for (const ElemSegment& seg : elemSegments_) {
         Table& table = *tables[seg.tableIndex];
         uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
         bool profilingEnabled = instance.code().profilingEnabled();
         const CodeRangeVector& codeRanges = metadata().codeRanges;
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -46,17 +46,16 @@ ReadI64Object(JSContext* cx, HandleValue
 
 struct LinkDataCacheablePod
 {
     uint32_t functionCodeLength;
     uint32_t globalDataLength;
     uint32_t interruptOffset;
     uint32_t outOfBoundsOffset;
     uint32_t unalignedAccessOffset;
-    uint32_t badIndirectCallOffset;
 
     LinkDataCacheablePod() { mozilla::PodZero(this); }
 };
 
 struct LinkData : LinkDataCacheablePod
 {
     LinkDataCacheablePod& pod() { return *this; }
     const LinkDataCacheablePod& pod() const { return *this; }
--- a/js/src/asmjs/WasmStubs.cpp
+++ b/js/src/asmjs/WasmStubs.cpp
@@ -911,17 +911,18 @@ GenerateThrow(MacroAssembler& masm)
 Offsets
 wasm::GenerateJumpTarget(MacroAssembler& masm, JumpTarget target)
 {
     switch (target) {
       case JumpTarget::StackOverflow:
         return GenerateStackOverflow(masm);
       case JumpTarget::Throw:
         return GenerateThrow(masm);
-      case JumpTarget::BadIndirectCall:
+      case JumpTarget::IndirectCallToNull:
+      case JumpTarget::IndirectCallBadSig:
       case JumpTarget::OutOfBounds:
       case JumpTarget::UnalignedAccess:
       case JumpTarget::Unreachable:
       case JumpTarget::IntegerOverflow:
       case JumpTarget::InvalidConversionToInteger:
       case JumpTarget::IntegerDivideByZero:
       case JumpTarget::ImpreciseSimdConversion:
         return GenerateTrapStub(masm, Trap(target));
--- a/js/src/asmjs/WasmTable.cpp
+++ b/js/src/asmjs/WasmTable.cpp
@@ -43,61 +43,42 @@ Table::create(JSContext* cx, const Table
         array = cx->pod_calloc<void*>(desc.initial);
     if (!array)
         return nullptr;
 
     table->maybeObject_.set(maybeObject);
     table->array_.reset((uint8_t*)array);
     table->kind_ = desc.kind;
     table->length_ = desc.initial;
-    table->initialized_ = false;
     table->external_ = desc.external;
     return table;
 }
 
 void
-Table::init(Instance& instance)
-{
-    MOZ_ASSERT(!initialized());
-    initialized_ = true;
-
-    void* code = instance.codeSegment().badIndirectCallCode();
-    if (external_) {
-        ExternalTableElem* array = externalArray();
-        TlsData* tls = &instance.tlsData();
-        for (uint32_t i = 0; i < length_; i++) {
-            array[i].code = code;
-            array[i].tls = tls;
-        }
-    } else {
-        void** array = internalArray();
-        for (uint32_t i = 0; i < length_; i++)
-            array[i] = code;
-    }
-}
-
-void
 Table::tracePrivate(JSTracer* trc)
 {
     // If this table has a WasmTableObject, then this method is only called by
     // WasmTableObject's trace hook so maybeObject_ must already be marked.
     // TraceEdge is called so that the pointer can be updated during a moving
     // GC. TraceWeakEdge may sound better, but it is less efficient given that
     // we know object_ is already marked.
     if (maybeObject_) {
         MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
         TraceEdge(trc, &maybeObject_, "wasm table object");
     }
 
-    if (!initialized_ || !external_)
-        return;
-
-    ExternalTableElem* array = externalArray();
-    for (uint32_t i = 0; i < length_; i++)
-        array[i].tls->instance->trace(trc);
+    if (external_) {
+        ExternalTableElem* array = externalArray();
+        for (uint32_t i = 0; i < length_; i++) {
+            if (array[i].tls)
+                array[i].tls->instance->trace(trc);
+            else
+                MOZ_ASSERT(!array[i].code);
+        }
+    }
 }
 
 void
 Table::trace(JSTracer* trc)
 {
     // The trace hook of WasmTableObject will call Table::tracePrivate at
     // which point we can mark the rest of the children. If there is no
     // WasmTableObject, call Table::tracePrivate directly. Redirecting through
@@ -107,46 +88,53 @@ Table::trace(JSTracer* trc)
         TraceEdge(trc, &maybeObject_, "wasm table object");
     else
         tracePrivate(trc);
 }
 
 void**
 Table::internalArray() const
 {
-    MOZ_ASSERT(initialized_);
     MOZ_ASSERT(!external_);
     return (void**)array_.get();
 }
 
 ExternalTableElem*
 Table::externalArray() const
 {
-    MOZ_ASSERT(initialized_);
     MOZ_ASSERT(external_);
     return (ExternalTableElem*)array_.get();
 }
 
 void
 Table::set(uint32_t index, void* code, Instance& instance)
 {
     if (external_) {
         ExternalTableElem& elem = externalArray()[index];
+        if (elem.tls)
+            JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
+
         elem.code = code;
         elem.tls = &instance.tlsData();
+
+        MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(), "no writeBarrierPost");
     } else {
         internalArray()[index] = code;
     }
 }
 
 void
 Table::setNull(uint32_t index)
 {
     // Only external tables can set elements to null after initialization.
     ExternalTableElem& elem = externalArray()[index];
-    elem.code = elem.tls->instance->codeSegment().badIndirectCallCode();
+    if (elem.tls)
+        JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
+
+    elem.code = nullptr;
+    elem.tls = nullptr;
 }
 
 size_t
 Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return mallocSizeOf(array_.get());
 }
--- a/js/src/asmjs/WasmTable.h
+++ b/js/src/asmjs/WasmTable.h
@@ -31,42 +31,34 @@ namespace wasm {
 class Table : public ShareableBase<Table>
 {
     typedef UniquePtr<uint8_t[], JS::FreePolicy> UniqueByteArray;
 
     ReadBarrieredWasmTableObject maybeObject_;
     UniqueByteArray              array_;
     TableKind                    kind_;
     uint32_t                     length_;
-    bool                         initialized_;
     bool                         external_;
 
     void tracePrivate(JSTracer* trc);
     friend class js::WasmTableObject;
 
   public:
     static RefPtr<Table> create(JSContext* cx, const TableDesc& desc,
                                 HandleWasmTableObject maybeObject);
     void trace(JSTracer* trc);
 
-    // These accessors may be used before initialization.
-
     bool external() const { return external_; }
     bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
     uint32_t length() const { return length_; }
     uint8_t* base() const { return array_.get(); }
 
-    // A Table must be initialized before any dependent instance can execute.
-
-    bool initialized() const { return initialized_; }
-    void init(Instance& instance);
-
-    // After initialization, elements must be accessed. All updates must go
-    // through a set() function with the exception of (profiling) updates to the
-    // callee pointer that do not change which logical function is being called.
+    // All updates must go through a set() function with the exception of
+    // (profiling) updates to the callee pointer that do not change which
+    // logical function is being called.
 
     void** internalArray() const;
     ExternalTableElem* externalArray() const;
     void set(uint32_t index, void* code, Instance& instance);
     void setNull(uint32_t index);
 
     // about:memory reporting:
 
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -112,18 +112,21 @@ HandleTrap(int32_t trapIndex)
         errorNumber = JSMSG_WASM_INTEGER_OVERFLOW;
         break;
       case Trap::InvalidConversionToInteger:
         errorNumber = JSMSG_WASM_INVALID_CONVERSION;
         break;
       case Trap::IntegerDivideByZero:
         errorNumber = JSMSG_WASM_INT_DIVIDE_BY_ZERO;
         break;
-      case Trap::BadIndirectCall:
-        errorNumber = JSMSG_WASM_BAD_IND_CALL;
+      case Trap::IndirectCallToNull:
+        errorNumber = JSMSG_WASM_IND_CALL_TO_NULL;
+        break;
+      case Trap::IndirectCallBadSig:
+        errorNumber = JSMSG_WASM_IND_CALL_BAD_SIG;
         break;
       case Trap::ImpreciseSimdConversion:
         errorNumber = JSMSG_SIMD_FAILED_CONVERSION;
         break;
       case Trap::OutOfBounds:
         errorNumber = JSMSG_BAD_INDEX;
         break;
       case Trap::UnalignedAccess:
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -870,18 +870,20 @@ enum class Trap
     // Trying to coerce NaN to an integer.
     InvalidConversionToInteger,
     // Integer division by zero.
     IntegerDivideByZero,
     // Out of bounds on wasm memory accesses and asm.js SIMD/atomic accesses.
     OutOfBounds,
     // Unaligned memory access.
     UnalignedAccess,
-    // Bad signature for an indirect call.
-    BadIndirectCall,
+    // call_indirect to null.
+    IndirectCallToNull,
+    // call_indirect signature mismatch.
+    IndirectCallBadSig,
 
     // (asm.js only) SIMD float to int conversion failed because the input
     // wasn't in bounds.
     ImpreciseSimdConversion,
 
     Limit
 };
 
@@ -894,17 +896,18 @@ enum class JumpTarget
 {
     // Traps
     Unreachable = unsigned(Trap::Unreachable),
     IntegerOverflow = unsigned(Trap::IntegerOverflow),
     InvalidConversionToInteger = unsigned(Trap::InvalidConversionToInteger),
     IntegerDivideByZero = unsigned(Trap::IntegerDivideByZero),
     OutOfBounds = unsigned(Trap::OutOfBounds),
     UnalignedAccess = unsigned(Trap::UnalignedAccess),
-    BadIndirectCall = unsigned(Trap::BadIndirectCall),
+    IndirectCallToNull = unsigned(Trap::IndirectCallToNull),
+    IndirectCallBadSig = unsigned(Trap::IndirectCallBadSig),
     ImpreciseSimdConversion = unsigned(Trap::ImpreciseSimdConversion),
     // Non-traps
     StackOverflow,
     Throw,
     Limit
 };
 
 typedef EnumeratedArray<JumpTarget, JumpTarget::Limit, Uint32Vector> JumpSiteArray;
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -712,16 +712,18 @@ PromiseObject::create(JSContext* cx, Han
         usedProto = CheckedUnwrap(proto);
         if (!usedProto)
             return nullptr;
     }
 
 
     // Steps 3-7.
     Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, wrappedProto));
+    if (!promise)
+        return nullptr;
 
     RootedValue promiseVal(cx, ObjectValue(*promise));
     if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
         return nullptr;
 
     // Step 8.
     // The resolving functions are created in the compartment active when the
     // (maybe wrapped) Promise constructor was called. They contain checks and
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -19,18 +19,18 @@
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::unicode;
 
+using mozilla::ArrayLength;
 using mozilla::CheckedInt;
-using mozilla::ArrayLength;
 using mozilla::Maybe;
 
 /*
  * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
  * steps 3, 16-25.
  */
 bool
 js::CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches,
@@ -182,18 +182,28 @@ CheckPatternSyntax(JSContext* cx, Handle
 
 enum RegExpSharedUse {
     UseRegExpShared,
     DontUseRegExpShared
 };
 
 /*
  * ES 2016 draft Mar 25, 2016 21.2.3.2.2.
- * Because this function only ever returns |obj| in the spec, provided by the
- * user, we omit it and just return the usual success/failure.
+ *
+ * Steps 14-15 set |obj|'s "lastIndex" property to zero.  Some of
+ * RegExpInitialize's callers have a fresh RegExp not yet exposed to script:
+ * in these cases zeroing "lastIndex" is infallible.  But others have a RegExp
+ * whose "lastIndex" property might have been made non-writable: here, zeroing
+ * "lastIndex" can fail.  We efficiently solve this problem by completely
+ * removing "lastIndex" zeroing from the provided function.
+ *
+ * CALLERS MUST HANDLE "lastIndex" ZEROING THEMSELVES!
+ *
+ * Because this function only ever returns a user-provided |obj| in the spec,
+ * we omit it and just return the usual success/failure.
  */
 static bool
 RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
                                   HandleValue patternValue, HandleValue flagsValue,
                                   RegExpSharedUse sharedUse = DontUseRegExpShared)
 {
     RootedAtom pattern(cx);
     if (patternValue.isUndefined()) {
@@ -342,16 +352,20 @@ regexp_compile_impl(JSContext* cx, const
         RootedValue P(cx, patternValue);
         RootedValue F(cx, args.get(1));
 
         // Step 5, minus lastIndex zeroing.
         if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
             return false;
     }
 
+    // The final niggling bit of step 5.
+    //
+    // |regexp| is user-exposed, but if its "lastIndex" property hasn't been
+    // made non-writable, we can still use a fast path to zero it.
     if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
         regexp->zeroLastIndex(cx);
     } else {
         RootedValue zero(cx, Int32Value(0));
         if (!SetProperty(cx, regexp, cx->names().lastIndex, zero))
             return false;
     }
 
new file mode 100755
--- /dev/null
+++ b/js/src/devtools/octane-csv.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+
+set -e -o pipefail
+
+function echo_to_stderr {
+    echo "$1" 1>&2
+}
+
+function usage_and_exit {
+    echo_to_stderr "Usage:"
+    echo_to_stderr "    $0 <path-to-js> <number-of-iterations>"
+    echo_to_stderr
+    echo_to_stderr "Run octane <number-of-iterations> times, and aggregate the results"
+    echo_to_stderr "into one CSV file, which is written to stdout."
+    echo_to_stderr
+    echo_to_stderr "See the js/src/devtools/plot-octane.R script for plotting the"
+    echo_to_stderr "results."
+    echo_to_stderr
+    echo_to_stderr "Complete example usage with plotting:"
+    echo_to_stderr
+    echo_to_stderr "    \$ ./js/src/devtools/octane-csv.sh path/to/js 20 > control.csv"
+    echo_to_stderr
+    echo_to_stderr "    Next, apply some patch you'd like to test."
+    echo_to_stderr
+    echo_to_stderr "    \$ ./js/src/devtools/octane-csv.sh path/to/js 20 > variable.csv"
+    echo_to_stderr "    \$ ./js/src/devtools/plot-octane.R control.csv variable.csv"
+    echo_to_stderr
+    echo_to_stderr "    Open Rplots.pdf to view the results."
+    exit 1
+}
+
+if [[ "$#" != "2" ]]; then
+    usage_and_exit
+fi
+
+# Get the absolute, normalized $JS path, and ensure its an executable.
+
+JS_DIR=$(dirname $1)
+if [[ ! -d "$JS_DIR" ]]; then
+    echo_to_stderr "error: no such directory $JS_DIR"
+    echo_to_stderr
+    usage_and_exit
+fi
+
+JS=$(basename $1)
+cd "$JS_DIR" > /dev/null
+JS="$(pwd)/$JS"
+if [[ ! -e "$JS" ]]; then
+    echo_to_stderr "error: '$JS' is not executable"
+    echo_to_stderr
+    usage_and_exit
+fi
+cd - > /dev/null
+
+# Go to the js/src/octane directory.
+
+cd $(dirname $0)/../octane > /dev/null
+
+# Run octane and transform the results into CSV.
+#
+# Run once as a warm up, and to grab the column headers. Then run the benchmark
+# $ITERS times, grabbing just the data rows.
+
+echo_to_stderr "Warm up"
+"$JS" ./run.js | grep -v -- "----" | cut -f 1 -d ':' | tr '\n' ','
+echo
+
+ITERS=$2
+while [[ "$ITERS" -ge "1" ]]; do
+    echo_to_stderr "Iterations left: $ITERS"
+    "$JS" ./run.js | grep -v -- "----" | cut -f 2 -d ':' | tr '\n' ','
+    echo
+    ITERS=$((ITERS - 1))
+done
+
+echo_to_stderr "All done :)"
new file mode 100755
--- /dev/null
+++ b/js/src/devtools/plot-octane.R
@@ -0,0 +1,38 @@
+#!/usr/bin/env Rscript
+
+# Usage:
+#
+#     octane.R control.csv variable.csv
+#
+# Output will be placed in Rplots.pdf
+#
+# Remember: on Octane, higher is better!
+
+library(ggplot2)
+
+args <- commandArgs(trailingOnly = TRUE)
+
+# Reading in data.
+control <- read.table(args[1], sep=",", header=TRUE)
+variable <- read.table(args[2], sep=",", header=TRUE)
+
+# Pulling out columns that we want to plot.
+# Not totally necessary.
+ctrl <- control$Score..version.9.
+var <- variable$Score..version.9.
+
+# Concatenating the values we want to plot.
+score <- c(ctrl, var)
+# Creating a vector of labels for the data points.
+label <- c(rep("control", length(ctrl)), rep("variable", length(var)))
+
+# Creating a data frame of the score and label.
+data <- data.frame(label, score)
+
+# Now plotting!
+ggplot(data, aes(label, score, color=label, pch=label)) +
+  # Adding boxplot without the outliers.
+  geom_boxplot(outlier.shape=NA) +
+  # Adding jitter plot on top of the boxplot. If you want to spread the points
+  # more, increase jitter.
+  geom_jitter(position=position_jitter(width=0.05)) 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -840,17 +840,22 @@ Parser<ParseHandler>::reportBadReturn(No
  */
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::checkStrictBinding(PropertyName* name, TokenPos pos)
 {
     if (!pc->sc()->needStrictChecks())
         return true;
 
-    if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) {
+    if (name == context->names().eval ||
+        name == context->names().arguments ||
+        name == context->names().let ||
+        name == context->names().static_ ||
+        IsKeyword(name))
+    {
         JSAutoByteString bytes;
         if (!AtomToPrintableString(context, name, &bytes))
             return false;
         return reportWithOffset(ParseStrictError, pc->sc()->strict(), pos.begin,
                                 JSMSG_BAD_BINDING, bytes.ptr());
     }
 
     return true;
@@ -2634,60 +2639,50 @@ Parser<ParseHandler>::functionArguments(
                     return false;
 
                 if (!noteDestructuredPositionalFormalParameter(funcpn, destruct))
                     return false;
 
                 break;
               }
 
-              case TOK_YIELD:
-                if (!checkYieldNameValidity())
-                    return false;
-                MOZ_ASSERT(yieldHandling == YieldIsName);
-                goto TOK_NAME;
-
-              case TOK_TRIPLEDOT: {
+              case TOK_TRIPLEDOT:
                 if (IsSetterKind(kind)) {
                     report(ParseError, false, null(),
                            JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
                     return false;
                 }
 
                 hasRest = true;
                 funbox->function()->setHasRest();
 
-                if (!tokenStream.getToken(&tt))
-                    return false;
-                // FIXME: This fails to handle a rest parameter named |yield|
-                //        correctly outside of generators: that is,
-                //        |var f = (...yield) => 42;| should be valid code!
-                //        When this is fixed, make sure to consult both
-                //        |yieldHandling| and |checkYieldNameValidity| for
-                //        correctness until legacy generator syntax is removed.
-                if (tt != TOK_NAME) {
-                    report(ParseError, false, null(), JSMSG_NO_REST_NAME);
-                    return false;
-                }
                 disallowDuplicateParams = true;
                 if (duplicatedParam) {
                     // Has duplicated args before the rest parameter.
                     report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
                     return false;
                 }
 
-                goto TOK_NAME;
-              }
-
-              TOK_NAME:
-              case TOK_NAME: {
+                if (!tokenStream.getToken(&tt))
+                    return false;
+                if (tt != TOK_NAME && tt != TOK_YIELD) {
+                    report(ParseError, false, null(), JSMSG_NO_REST_NAME);
+                    return false;
+                }
+                MOZ_FALLTHROUGH;
+
+              case TOK_NAME:
+              case TOK_YIELD: {
                 if (parenFreeArrow)
                     funbox->setStart(tokenStream);
 
-                RootedPropertyName name(context, tokenStream.currentName());
+                RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+                if (!name)
+                    return false;
+
                 if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams,
                                                    &duplicatedParam))
                 {
                     return false;
                 }
 
                 break;
               }
@@ -3337,29 +3332,16 @@ Parser<ParseHandler>::functionFormalPara
     handler.setEndPosition(body, pos().begin);
     handler.setEndPosition(pn, pos().end);
     handler.setFunctionBody(pn, body);
 
     return true;
 }
 
 template <typename ParseHandler>
-bool
-Parser<ParseHandler>::checkYieldNameValidity()
-{
-    // In star generators and in JS >= 1.7, yield is a keyword.  Otherwise in
-    // strict mode, yield is a future reserved word.
-    if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc()->strict()) {
-        report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
-        return false;
-    }
-    return true;
-}
-
-template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
     // Annex B.3.4 says we can parse function declarations unbraced under if
     // or else as if it were braced. That is, |if (x) function f() {}| is
     // parsed as |if (x) { function f() {} }|.
@@ -3382,22 +3364,20 @@ Parser<ParseHandler>::functionStmt(Yield
         return null();
 
     if (tt == TOK_MUL) {
         generatorKind = StarGenerator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
-    if (tt == TOK_NAME) {
-        name = tokenStream.currentName();
-    } else if (tt == TOK_YIELD) {
-        if (!checkYieldNameValidity())
-            return null();
-        name = tokenStream.currentName();
+    if (tt == TOK_NAME || tt == TOK_YIELD) {
+        name = bindingIdentifier(yieldHandling);
+        if (!name)
+            return null();
     } else if (defaultHandling == AllowDefaultName) {
         name = context->names().starDefaultStar;
         tokenStream.ungetToken();
     } else {
         /* Unnamed function expressions are forbidden in statement context. */
         report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT);
         return null();
     }
@@ -3431,22 +3411,20 @@ Parser<ParseHandler>::functionExpr(Invok
 
     if (tt == TOK_MUL) {
         generatorKind = StarGenerator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     RootedPropertyName name(context);
-    if (tt == TOK_NAME) {
-        name = tokenStream.currentName();
-    } else if (tt == TOK_YIELD) {
-        if (!checkYieldNameValidity())
-            return null();
-        name = tokenStream.currentName();
+    if (tt == TOK_NAME || tt == TOK_YIELD) {
+        name = bindingIdentifier(YieldIsName);
+        if (!name)
+            return null();
     } else {
         tokenStream.ungetToken();
     }
 
     YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
     return functionDefinition(InAllowed, yieldHandling, name, Expression, generatorKind, invoked);
 }
 
@@ -3683,29 +3661,22 @@ Parser<ParseHandler>::condition(InHandli
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::matchLabel(YieldHandling yieldHandling, MutableHandle<PropertyName*> label)
 {
     TokenKind tt = TOK_EOF;
     if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
         return false;
 
-    if (tt == TOK_NAME) {
-        tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
-        MOZ_ASSERT_IF(tokenStream.currentName() == context->names().yield,
-                      yieldHandling == YieldIsName);
-        label.set(tokenStream.currentName());
-    } else if (tt == TOK_YIELD) {
-        // We might still consider |yield| to be valid here, contrary to ES6.
-        // Fix bug 1104014, then stop shipping legacy generators in chrome
-        // code, then remove this check!
-        tokenStream.consumeKnownToken(TOK_YIELD, TokenStream::Operand);
-        if (!checkYieldNameValidity())
+    if (tt == TOK_NAME || tt == TOK_YIELD) {
+        tokenStream.consumeKnownToken(tt, TokenStream::Operand);
+
+        label.set(labelIdentifier(yieldHandling));
+        if (!label)
             return false;
-        label.set(tokenStream.currentName());
     } else {
         label.set(nullptr);
     }
     return true;
 }
 
 template <typename ParseHandler>
 Parser<ParseHandler>::PossibleError::PossibleError(Parser<ParseHandler>& parser)
@@ -4195,29 +4166,26 @@ Parser<ParseHandler>::initializerInNameD
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::declarationName(Node decl, DeclarationKind declKind, TokenKind tt,
                                       bool initialDeclaration, YieldHandling yieldHandling,
                                       ParseNodeKind* forHeadKind, Node* forInOrOfExpression)
 {
-    if (tt != TOK_NAME) {
-        // Anything other than TOK_YIELD or TOK_NAME is an error.
-        if (tt != TOK_YIELD) {
-            report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME);
-            return null();
-        }
-
-        // TOK_YIELD is only okay if it's treated as a name.
-        if (!checkYieldNameValidity())
-            return null();
-    }
-
-    RootedPropertyName name(context, tokenStream.currentName());
+    // Anything other than TOK_YIELD or TOK_NAME is an error.
+    if (tt != TOK_NAME && tt != TOK_YIELD) {
+        report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME);
+        return null();
+    }
+
+    RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+    if (!name)
+        return null();
+
     Node binding = newName(name);
     if (!binding)
         return null();
 
     TokenPos namePos = pos();
 
     // The '=' context after a variable name in a declaration is an opportunity
     // for ASI, and thus for the next token to start an ExpressionStatement:
@@ -4380,48 +4348,68 @@ Parser<FullParseHandler>::namedImportsOr
 
             if (tt == TOK_RC)
                 break;
 
             // If the next token is a keyword, the previous call to
             // peekToken matched it as a TOK_NAME, and put it in the
             // lookahead buffer, so this call will match keywords as well.
             MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_IMPORT_NAME);
-            Node importName = newName(tokenStream.currentName());
-            if (!importName)
-                return false;
-
-            bool foundAs;
-            if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as))
-                return false;
-
-            if (foundAs) {
-                MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
+            Rooted<PropertyName*> importName(context, tokenStream.currentName());
+            TokenPos importNamePos = pos();
+
+            TokenKind maybeAs;
+            if (!tokenStream.peekToken(&maybeAs))
+                return null();
+
+            if (maybeAs == TOK_NAME &&
+                tokenStream.nextName() == context->names().as)
+            {
+                tokenStream.consumeKnownToken(TOK_NAME);
+
+                if (!checkUnescapedName())
+                    return false;
+
+                TokenKind afterAs;
+                if (!tokenStream.getToken(&afterAs))
+                    return false;
+
+                if (afterAs != TOK_NAME && afterAs != TOK_YIELD) {
+                    report(ParseError, false, null(), JSMSG_NO_BINDING_NAME);
+                    return false;
+                }
             } else {
                 // Keywords cannot be bound to themselves, so an import name
                 // that is a keyword is a syntax error if it is not followed
                 // by the keyword 'as'.
                 // See the ImportSpecifier production in ES6 section 15.2.2.
-                if (IsKeyword(importName->name())) {
+                if (IsKeyword(importName)) {
                     JSAutoByteString bytes;
-                    if (!AtomToPrintableString(context, importName->name(), &bytes))
+                    if (!AtomToPrintableString(context, importName, &bytes))
                         return false;
                     report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr());
                     return false;
                 }
             }
 
-            RootedPropertyName bindingAtom(context, tokenStream.currentName());
+            RootedPropertyName bindingAtom(context, importedBinding());
+            if (!bindingAtom)
+                return false;
+
             Node bindingName = newName(bindingAtom);
             if (!bindingName)
                 return false;
             if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
                 return false;
 
-            Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
+            Node importNameNode = newName(importName, importNamePos);
+            if (!importNameNode)
+                return false;
+
+            Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importNameNode, bindingName);
             if (!importSpec)
                 return false;
 
             handler.addList(importSpecSet, importSpec);
 
             bool matched;
             if (!tokenStream.matchToken(&matched, TOK_COMMA))
                 return false;
@@ -4451,17 +4439,19 @@ Parser<FullParseHandler>::namedImportsOr
         Node importName = newName(context->names().star);
         if (!importName)
             return false;
 
         // Namespace imports are are not indirect bindings but lexical
         // definitions that hold a module namespace object. They are treated
         // as const variables which are initialized during the
         // ModuleDeclarationInstantiation step.
-        RootedPropertyName bindingName(context, tokenStream.currentName());
+        RootedPropertyName bindingName(context, importedBinding());
+        if (!bindingName)
+            return false;
         Node bindingNameNode = newName(bindingName);
         if (!bindingNameNode)
             return false;
         if (!noteDeclaredName(bindingName, DeclarationKind::Const, pos()))
             return false;
 
         // The namespace import name is currently required to live on the
         // environment.
@@ -4510,20 +4500,24 @@ Parser<FullParseHandler>::importDeclarat
             // Handle the form |import a from 'b'|, by adding a single import
             // specifier to the list, with 'default' as the import name and
             // 'a' as the binding name. This is equivalent to
             // |import { default as a } from 'b'|.
             Node importName = newName(context->names().default_);
             if (!importName)
                 return null();
 
-            RootedPropertyName bindingAtom(context, tokenStream.currentName());
+            RootedPropertyName bindingAtom(context, importedBinding());
+            if (!bindingAtom)
+                return null();
+
             Node bindingName = newName(bindingAtom);
             if (!bindingName)
                 return null();
+
             if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
                 return null();
 
             Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
             if (!importSpec)
                 return null();
 
             handler.addList(importSpecSet, importSpec);
@@ -4865,25 +4859,38 @@ Parser<FullParseHandler>::exportDeclarat
         ParseNode* node = handler.newExportDefaultDeclaration(kid, nameNode,
                                                               TokenPos(begin, pos().end));
         if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
             return null();
 
         return node;
       }
 
-      case TOK_LET:
       case TOK_CONST:
-        kid = lexicalDeclaration(YieldIsName, tt == TOK_CONST);
+        kid = lexicalDeclaration(YieldIsName, /* isConst = */ true);
         if (!kid)
             return null();
         if (!checkExportedNamesForDeclaration(kid))
             return null();
         break;
 
+      case TOK_NAME:
+        if (tokenStream.currentName() == context->names().let) {
+            if (!checkUnescapedName())
+                return null();
+
+            kid = lexicalDeclaration(YieldIsName, /* isConst = */ false);
+            if (!kid)
+                return null();
+            if (!checkExportedNamesForDeclaration(kid))
+                return null();
+            break;
+        }
+        MOZ_FALLTHROUGH;
+
       default:
         report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_EXPORT);
         return null();
     }
 
     ParseNode* node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end));
     if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
         return null();
@@ -5119,26 +5126,25 @@ Parser<ParseHandler>::forHeadStart(Yield
         return *forInitialPart != null();
     }
 
     // Otherwise we have a lexical declaration or an expression.
 
     // For-in loop backwards compatibility requires that |let| starting a
     // for-loop that's not a (new to ES6) for-of loop, in non-strict mode code,
     // parse as an identifier.  (|let| in for-of is always a declaration.)
-    // Thus we must can't just sniff out TOK_CONST/TOK_LET here.  :-(
     bool parsingLexicalDeclaration = false;
     bool letIsIdentifier = false;
-    if (tt == TOK_LET || tt == TOK_CONST) {
+    if (tt == TOK_CONST) {
         parsingLexicalDeclaration = true;
         tokenStream.consumeKnownToken(tt, TokenStream::Operand);
-    } else if (tt == TOK_NAME && tokenStream.nextName() == context->names().let) {
-        MOZ_ASSERT(!pc->sc()->strict(),
-                   "should parse |let| as TOK_LET in strict mode code");
-
+    } else if (tt == TOK_NAME &&
+               tokenStream.nextName() == context->names().let &&
+               !tokenStream.nextNameContainsEscape())
+    {
         // We could have a {For,Lexical}Declaration, or we could have a
         // LeftHandSideExpression with lookahead restrictions so it's not
         // ambiguous with the former.  Check for a continuation of the former
         // to decide which we have.
         tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
 
         TokenKind next;
         if (!tokenStream.peekToken(&next))
@@ -5870,28 +5876,31 @@ Parser<ParseHandler>::labeledItem(YieldH
     tokenStream.ungetToken();
     return statement(yieldHandling);
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::labeledStatement(YieldHandling yieldHandling)
 {
-    uint32_t begin = pos().begin;
-    RootedPropertyName label(context, tokenStream.currentName());
+    RootedPropertyName label(context, labelIdentifier(yieldHandling));
+    if (!label)
+        return null();
 
     auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
         return stmt->label() == label;
     };
 
     if (pc->findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) {
         report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL);
         return null();
     }
 
+    uint32_t begin = pos().begin;
+
     tokenStream.consumeKnownToken(TOK_COLON);
 
     /* Push a label struct and parse the statement. */
     ParseContext::LabelStatement stmt(pc, label);
     Node pn = labeledItem(yieldHandling);
     if (!pn)
         return null();
 
@@ -6016,29 +6025,22 @@ Parser<ParseHandler>::tryStatement(Yield
             switch (tt) {
               case TOK_LB:
               case TOK_LC:
                 catchName = destructuringDeclaration(DeclarationKind::CatchParameter,
                                                      yieldHandling, tt);
                 if (!catchName)
                     return null();
                 break;
-              case TOK_YIELD:
-                if (yieldHandling == YieldIsKeyword) {
-                    report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
+
+              case TOK_NAME:
+              case TOK_YIELD: {
+                RootedPropertyName param(context, bindingIdentifier(yieldHandling));
+                if (!param)
                     return null();
-                }
-
-                // Even if yield is *not* necessarily a keyword, we still must
-                // check its validity for legacy generators.
-                if (!checkYieldNameValidity())
-                    return null();
-                MOZ_FALLTHROUGH;
-              case TOK_NAME: {
-                RootedPropertyName param(context, tokenStream.currentName());
                 catchName = newName(param);
                 if (!catchName)
                     return null();
                 if (!noteDeclaredName(param, DeclarationKind::SimpleCatchParameter, pos()))
                     return null();
                 break;
               }
 
@@ -6228,23 +6230,20 @@ Parser<ParseHandler>::classDefinition(Yi
 
     bool savedStrictness = setLocalStrictMode(true);
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     RootedPropertyName name(context);
-    if (tt == TOK_NAME) {
-        name = tokenStream.currentName();
-    } else if (tt == TOK_YIELD) {
-        if (!checkYieldNameValidity())
-            return null();
-        MOZ_ASSERT(yieldHandling != YieldIsKeyword);
-        name = tokenStream.currentName();
+    if (tt == TOK_NAME || tt == TOK_YIELD) {
+        name = bindingIdentifier(yieldHandling);
+        if (!name)
+            return null();
     } else if (classContext == ClassStatement) {
         if (defaultHandling == AllowDefaultName) {
             name = context->names().starDefaultStar;
             tokenStream.ungetToken();
         } else {
             // Class statements must have a bound name
             report(ParseError, false, null(), JSMSG_UNNAMED_CLASS_STMT);
             return null();
@@ -6303,16 +6302,23 @@ Parser<ParseHandler>::classDefinition(Yi
         if (tt == TOK_RC)
             break;
 
         if (tt == TOK_SEMI)
             continue;
 
         bool isStatic = false;
         if (tt == TOK_NAME && tokenStream.currentName() == context->names().static_) {
+            MOZ_ASSERT(pc->sc()->strict(), "classes are always strict");
+
+            // Strict mode forbids "class" as Identifier, so it can only be the
+            // unescaped keyword.
+            if (!checkUnescapedName())
+                return null();
+
             if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
                 return null();
             if (tt == TOK_RC) {
                 tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName);
                 report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                        "property name", TokenKindToDesc(tt));
                 return null();
             }
@@ -6433,60 +6439,61 @@ Parser<ParseHandler>::classDefinition(Yi
 
     return handler.newClass(nameNode, classHeritage, methodsOrBlock);
 }
 
 template <class ParseHandler>
 bool
 Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
 {
-    MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME),
-               "TOK_LET should have been summarily considered a "
-               "LexicalDeclaration");
+    MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME));
     MOZ_ASSERT(tokenStream.currentName() == context->names().let);
+    MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape());
 
 #ifdef DEBUG
     TokenKind verify;
     MOZ_ALWAYS_TRUE(tokenStream.peekToken(&verify));
     MOZ_ASSERT(next == verify);
 #endif
 
     // Destructuring is (for once) the easy case.
     if (next == TOK_LB || next == TOK_LC)
         return true;
 
     // Otherwise a let declaration must have a name.
     if (next == TOK_NAME) {
+        MOZ_ASSERT(tokenStream.nextName() != context->names().yield,
+                   "token stream should interpret 'yield' as TOK_YIELD");
+
         // One non-"yield" TOK_NAME edge case deserves special comment.
         // Consider this:
         //
         //   let     // not an ASI opportunity
         //   let;
         //
         // Static semantics in ยง13.3.1.1 turn a LexicalDeclaration that binds
         // "let" into an early error.  Does this retroactively permit ASI so
         // that we should parse this as two ExpressionStatements?   No.  ASI
         // resolves during parsing.  Static semantics only apply to the full
         // parse tree with ASI applied.  No backsies!
-        if (tokenStream.nextName() != context->names().yield)
-            return true;
-    } else if (next != TOK_YIELD) {
-        return false;
-    }
-
-    // We have the name "yield": the grammar parameter exactly states whether
-    // this is okay.  Even if YieldIsKeyword, the code might be valid if ASI
-    // induces a preceding semicolon.  If YieldIsName, the code is valid
-    // outside strict mode, and declaration-parsing code will enforce strict
-    // mode restrictions.
-    //
-    // No checkYieldNameValidity for TOK_YIELD is needed here.  It'll happen
-    // when TOK_YIELD is consumed as BindingIdentifier or as start of a fresh
-    // Statement.
-    return yieldHandling == YieldIsName;
+        return true;
+    }
+
+    // If we have the name "yield", the grammar parameter exactly states
+    // whether this is okay.  (This wasn't true for SpiderMonkey's ancient
+    // legacy generator syntax, but that's dead now.)  If YieldIsName,
+    // declaration-parsing code will (if necessary) enforce a strict mode
+    // restriction on defining "yield".  If YieldIsKeyword, consider this the
+    // end of the declaration, in case ASI induces a semicolon that makes the
+    // "yield" valid.
+    if (next == TOK_YIELD)
+        return yieldHandling == YieldIsName;
+
+    // Otherwise not a let declaration.
+    return false;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::variableStatement(YieldHandling yieldHandling)
 {
     Node vars = declarationList(yieldHandling, PNK_VAR);
     if (!vars)
@@ -6530,48 +6537,43 @@ Parser<ParseHandler>::statement(YieldHan
         if (yieldExpressionsSupported())
             modifier = TokenStream::Operand;
         else
             modifier = TokenStream::None;
 
         TokenKind next;
         if (!tokenStream.peekToken(&next, modifier))
             return null();
-        if (next == TOK_COLON) {
-            if (!checkYieldNameValidity())
-                return null();
+
+        if (next == TOK_COLON)
             return labeledStatement(yieldHandling);
-        }
+
         return expressionStatement(yieldHandling);
       }
 
       case TOK_NAME: {
         TokenKind next;
         if (!tokenStream.peekToken(&next))
             return null();
 
-#ifdef DEBUG
-        if (tokenStream.currentName() == context->names().let) {
-            MOZ_ASSERT(!pc->sc()->strict(),
-                       "observing |let| as TOK_NAME and not TOK_LET implies "
-                       "non-strict code (and the edge case of 'use strict' "
-                       "immediately followed by |let| on a new line only "
-                       "applies to StatementListItems, not to Statements)");
-        }
-#endif
-
-        // Statement context forbids LexicalDeclaration.
-        if ((next == TOK_LB || next == TOK_LC || next == TOK_NAME) &&
+        // |let| here can only be an Identifier, not a declaration.  Give nicer
+        // errors for declaration-looking typos.
+        if (!tokenStream.currentToken().nameContainsEscape() &&
             tokenStream.currentName() == context->names().let)
         {
             bool forbiddenLetDeclaration = false;
-            if (next == TOK_LB) {
-                // ExpressionStatement has a 'let [' lookahead restriction.
+
+            if (pc->sc()->strict() || versionNumber() >= JSVERSION_1_7) {
+                // |let| can't be an Identifier in strict mode code.  Ditto for
+                // non-standard JavaScript 1.7+.
                 forbiddenLetDeclaration = true;
-            } else {
+            } else if (next == TOK_LB) {
+                // Enforce ExpressionStatement's 'let [' lookahead restriction.
+                forbiddenLetDeclaration = true;
+            } else if (next == TOK_LC || next == TOK_NAME) {
                 // 'let {' and 'let foo' aren't completely forbidden, if ASI
                 // causes 'let' to be the entire Statement.  But if they're
                 // same-line, we can aggressively give a better error message.
                 //
                 // Note that this ignores 'yield' as TOK_YIELD: we'll handle it
                 // correctly but with a worse error message.
                 TokenKind nextSameLine;
                 if (!tokenStream.peekTokenSameLine(&nextSameLine))
@@ -6689,24 +6691,16 @@ Parser<ParseHandler>::statement(YieldHan
       case TOK_CATCH:
         report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY);
         return null();
 
       case TOK_FINALLY:
         report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY);
         return null();
 
-      // TOK_LET implies we're in strict mode code where static semantics
-      // forbid IdentifierName to be "let": a stronger restriction than
-      // Statement's lookahead restriction on |let [|.  Provide a better error
-      // message here than the default case would.
-      case TOK_LET:
-        report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "let declarations");
-        return null();
-
       // NOTE: default case handled in the ExpressionStatement section.
     }
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
                                         bool canHaveDirectives /* = false */)
@@ -6752,46 +6746,33 @@ Parser<ParseHandler>::statementListItem(
         if (yieldExpressionsSupported())
             modifier = TokenStream::Operand;
         else
             modifier = TokenStream::None;
 
         TokenKind next;
         if (!tokenStream.peekToken(&next, modifier))
             return null();
-        if (next == TOK_COLON) {
-            if (!checkYieldNameValidity())
-                return null();
+
+        if (next == TOK_COLON)
             return labeledStatement(yieldHandling);
-        }
+
         return expressionStatement(yieldHandling);
       }
 
       case TOK_NAME: {
         TokenKind next;
         if (!tokenStream.peekToken(&next))
             return null();
 
-        if (tokenStream.currentName() == context->names().let) {
-            if (nextTokenContinuesLetDeclaration(next, yieldHandling))
-                return lexicalDeclaration(yieldHandling, /* isConst = */ false);
-
-            // IdentifierName can't be "let" in strict mode code.  |let| in
-            // strict mode code is usually TOK_LET, but in this one weird case
-            // in global code it's TOK_NAME:
-            //
-            //   "use strict"              // ExpressionStatement ended by ASI
-            //   let <...whatever else...> // a fresh StatementListItem
-            //
-            // Carefully reject strict mode |let| non-declarations.
-            if (pc->sc()->strict()) {
-                report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
-                       "declaration pattern", TokenKindToDesc(next));
-                return null();
-            }
+        if (!tokenStream.currentToken().nameContainsEscape() &&
+            tokenStream.currentName() == context->names().let &&
+            nextTokenContinuesLetDeclaration(next, yieldHandling))
+        {
+            return lexicalDeclaration(yieldHandling, /* isConst = */ false);
         }
 
         if (next == TOK_COLON)
             return labeledStatement(yieldHandling);
 
         return expressionStatement(yieldHandling);
       }
 
@@ -6867,21 +6848,20 @@ Parser<ParseHandler>::statementListItem(
         return functionStmt(yieldHandling, NameRequired);
 
       //   ClassDeclaration[?Yield, ~Default]
       case TOK_CLASS:
         return classDefinition(yieldHandling, ClassStatement, NameRequired);
 
       //   LexicalDeclaration[In, ?Yield]
       //     LetOrConst BindingList[?In, ?Yield]
-      case TOK_LET:
       case TOK_CONST:
         // [In] is the default behavior, because for-loops specially parse
         // their heads to handle |in| in this situation.
-        return lexicalDeclaration(yieldHandling, /* isConst = */ tt == TOK_CONST);
+        return lexicalDeclaration(yieldHandling, /* isConst = */ true);
 
       // ImportDeclaration (only inside modules)
       case TOK_IMPORT:
         return importDeclaration();
 
       // ExportDeclaration (only inside modules)
       case TOK_EXPORT:
         return exportDeclaration();
@@ -7243,18 +7223,23 @@ Parser<ParseHandler>::assignExpr(InHandl
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
 
     bool endsExpr;
 
     if (tt == TOK_NAME) {
         if (!tokenStream.nextTokenEndsExpr(&endsExpr))
             return null();
-        if (endsExpr)
-            return identifierName(yieldHandling);
+        if (endsExpr) {
+            Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+            if (!name)
+                return null();
+
+            return identifierReference(name);
+        }
     }
 
     if (tt == TOK_NUMBER) {
         if (!tokenStream.nextTokenEndsExpr(&endsExpr))
             return null();
         if (endsExpr)
             return newNumber(tokenStream.currentToken());
     }
@@ -8253,28 +8238,109 @@ Parser<ParseHandler>::newName(PropertyNa
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos)
 {
     return handler.newName(name, pos, context);
 }
 
 template <typename ParseHandler>
+PropertyName*
+Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling)
+{
+    PropertyName* ident;
+    const Token& tok = tokenStream.currentToken();
+    if (tok.type == TOK_NAME) {
+        ident = tok.name();
+        MOZ_ASSERT(ident != context->names().yield,
+                   "tokenizer should have treated 'yield' as TOK_YIELD");
+
+        if (pc->sc()->strict()) {
+            const char* badName = ident == context->names().let
+                                  ? "let"
+                                  : ident == context->names().static_
+                                  ? "static"
+                                  : nullptr;
+            if (badName) {
+                report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
+                return nullptr;
+            }
+        }
+    } else {
+        MOZ_ASSERT(tok.type == TOK_YIELD);
+
+        if (yieldHandling == YieldIsKeyword ||
+            pc->sc()->strict() ||
+            pc->isStarGenerator() ||
+            versionNumber() >= JSVERSION_1_7)
+        {
+            report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
+            return nullptr;
+        }
+
+        ident = context->names().yield;
+    }
+
+    return ident;
+}
+
+template <typename ParseHandler>
+PropertyName*
+Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling)
+{
+    PropertyName* ident;
+    const Token& tok = tokenStream.currentToken();
+    if (tok.type == TOK_NAME) {
+        ident = tok.name();
+        MOZ_ASSERT(ident != context->names().yield,
+                   "tokenizer should have treated 'yield' as TOK_YIELD");
+
+        if (pc->sc()->strict()) {
+            const char* badName = ident == context->names().arguments
+                                  ? "arguments"
+                                  : ident == context->names().eval
+                                  ? "eval"
+                                  : nullptr;
+            if (badName) {
+                report(ParseError, false, null(), JSMSG_BAD_STRICT_ASSIGN, badName);
+                return nullptr;
+            }
+
+            badName = ident == context->names().let
+                      ? "let"
+                      : ident == context->names().static_
+                      ? "static"
+                      : nullptr;
+            if (badName) {
+                report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
+                return nullptr;
+            }
+        }
+    } else {
+        MOZ_ASSERT(tok.type == TOK_YIELD);
+
+        if (yieldHandling == YieldIsKeyword ||
+            pc->sc()->strict() ||
+            pc->isStarGenerator() ||
+            versionNumber() >= JSVERSION_1_7)
+        {
+            report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
+            return nullptr;
+        }
+
+        ident = context->names().yield;
+    }
+
+    return ident;
+}
+
+template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::identifierName(YieldHandling yieldHandling)
-{
-    RootedPropertyName name(context, tokenStream.currentName());
-    if (yieldHandling == YieldIsKeyword && name == context->names().yield) {
-        report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
-        return null();
-    }
-
-    // If we're inside a function that later becomes a legacy generator, then
-    // a |yield| identifier name here will be detected by a subsequent
-    // |checkYieldNameValidity| call.
+Parser<ParseHandler>::identifierReference(Handle<PropertyName*> name)
+{
     Node pn = newName(name);
     if (!pn)
         return null();
 
     if (!pc->inDestructuringDecl && !noteUsedName(name))
         return null();
 
     return pn;
@@ -8665,40 +8731,48 @@ Parser<ParseHandler>::objectLiteral(Yiel
         } else if (propType == PropertyType::Shorthand) {
             /*
              * Support, e.g., |var {x, y} = o| as destructuring shorthand
              * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
              */
             if (!tokenStream.checkForKeyword(propAtom, nullptr))
                 return null();
 
-            Node nameExpr = identifierName(yieldHandling);
+            Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+            if (!name)
+                return null();
+
+            Node nameExpr = identifierReference(name);
             if (!nameExpr)
                 return null();
 
             if (!handler.addShorthand(literal, propName, nameExpr))
                 return null();
         } else if (propType == PropertyType::CoverInitializedName) {
             /*
              * Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand
              * with default values, as per ES6 12.14.5 (2016/2/4)
              */
             if (!tokenStream.checkForKeyword(propAtom, nullptr))
                 return null();
 
-            Node lhs = identifierName(yieldHandling);
+            Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+            if (!name)
+                return null();
+
+            Node lhs = identifierReference(name);
             if (!lhs)
                 return null();
 
             tokenStream.consumeKnownToken(TOK_ASSIGN);
 
             Node rhs;
             {
                 // Clearing `inDestructuringDecl` allows name use to be noted
-                // in `identifierName`. See bug 1255167.
+                // in Parser::identifierReference. See bug 1255167.
                 AutoClearInDestructuringDecl autoClear(pc);
                 rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
                 if (!rhs)
                     return null();
             }
 
             Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
             if (!propExpr)
@@ -8895,21 +8969,23 @@ Parser<ParseHandler>::primaryExpr(YieldH
 
       case TOK_NO_SUBS_TEMPLATE:
         return noSubstitutionTemplate();
 
       case TOK_STRING:
         return stringLiteral();
 
       case TOK_YIELD:
-        if (!checkYieldNameValidity())
-            return null();
-        MOZ_FALLTHROUGH;
-      case TOK_NAME:
-        return identifierName(yieldHandling);
+      case TOK_NAME: {
+        Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+        if (!name)
+            return null();
+
+        return identifierReference(name);
+      }
 
       case TOK_REGEXP:
         return newRegExp();
 
       case TOK_NUMBER:
         return newNumber(tokenStream.currentToken());
 
       case TOK_TRUE:
@@ -8941,22 +9017,21 @@ Parser<ParseHandler>::primaryExpr(YieldH
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "expression", TokenKindToDesc(tt));
             return null();
         }
 
         TokenKind next;
         if (!tokenStream.getToken(&next))
             return null();
-        // FIXME: This fails to handle a rest parameter named |yield| correctly
-        //        outside of generators: |var f = (...yield) => 42;| should be
-        //        valid code!  When this is fixed, make sure to consult both
-        //        |yieldHandling| and |checkYieldNameValidity| for correctness
-        //        until legacy generator syntax is removed.
-        if (next != TOK_NAME) {
+
+        // This doesn't check that the provided name is allowed, e.g. if the
+        // enclosing code is strict mode code, any of "arguments", "let", or
+        // "yield" should be prohibited.  Argument-parsing code handles that.
+        if (next != TOK_NAME && next != TOK_YIELD) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "rest argument name", TokenKindToDesc(next));
             return null();
         }
 
         if (!tokenStream.getToken(&next))
             return null();
         if (next != TOK_RP) {
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -976,21 +976,25 @@ class Parser final : private JS::AutoGCR
     // Parse a function's formal parameters and its body assuming its function
     // ParseContext is already on the stack.
     bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling,
                                          Node pn, FunctionSyntaxKind kind);
 
     // Determine whether |yield| is a valid name in the current context, or
     // whether it's prohibited due to strictness, JS version, or occurrence
     // inside a star generator.
-    bool checkYieldNameValidity();
     bool yieldExpressionsSupported() {
         return versionNumber() >= JSVERSION_1_7 || pc->isGenerator();
     }
 
+    // Match the current token against the BindingIdentifier production with
+    // the given Yield parameter.  If there is no match, report a syntax
+    // error.
+    PropertyName* bindingIdentifier(YieldHandling yieldHandling);
+
     virtual bool strictMode() { return pc->sc()->strict(); }
     bool setLocalStrictMode(bool strict) {
         MOZ_ASSERT(tokenStream.debugHasNoLookahead());
         return pc->sc()->setLocalStrictMode(strict);
     }
 
     const ReadOnlyCompileOptions& options() const {
         return tokenStream.options();
@@ -1201,17 +1205,31 @@ class Parser final : private JS::AutoGCR
     bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet);
     bool checkExportedName(JSAtom* exportName);
     bool checkExportedNamesForDeclaration(Node node);
 
     enum ClassContext { ClassStatement, ClassExpression };
     Node classDefinition(YieldHandling yieldHandling, ClassContext classContext,
                          DefaultHandling defaultHandling);
 
-    Node identifierName(YieldHandling yieldHandling);
+    PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling);
+
+    PropertyName* labelIdentifier(YieldHandling yieldHandling) {
+        return labelOrIdentifierReference(yieldHandling);
+    }
+
+    PropertyName* identifierReference(YieldHandling yieldHandling) {
+        return labelOrIdentifierReference(yieldHandling);
+    }
+
+    PropertyName* importedBinding() {
+        return bindingIdentifier(YieldIsName);
+    }
+
+    Node identifierReference(Handle<PropertyName*> name);
 
     bool matchLabel(YieldHandling yieldHandling, MutableHandle<PropertyName*> label);
 
     bool allowsForEachIn() {
 #if !JS_HAS_FOR_EACH_IN
         return false;
 #else
         return versionNumber() >= JSVERSION_1_6;
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -102,17 +102,16 @@
     macro(NEW,          "keyword 'new'") \
     macro(DELETE,       "keyword 'delete'") \
     macro(TRY,          "keyword 'try'") \
     macro(CATCH,        "keyword 'catch'") \
     macro(FINALLY,      "keyword 'finally'") \
     macro(THROW,        "keyword 'throw'") \
     macro(DEBUGGER,     "keyword 'debugger'") \
     macro(YIELD,        "keyword 'yield'") \
-    macro(LET,          "keyword 'let'") \
     macro(EXPORT,       "keyword 'export'") \
     macro(IMPORT,       "keyword 'import'") \
     macro(CLASS,        "keyword 'class'") \
     macro(EXTENDS,      "keyword 'extends'") \
     macro(SUPER,        "keyword 'super'") \
     macro(RESERVED,     "reserved keyword") \
     /* reserved keywords in strict mode */ \
     macro(STRICT_RESERVED, "reserved keyword") \
@@ -234,18 +233,12 @@ TokenKindIsShift(TokenKind tt)
 }
 
 inline bool
 TokenKindIsAssignment(TokenKind tt)
 {
     return TOK_ASSIGNMENT_START <= tt && tt <= TOK_ASSIGNMENT_LAST;
 }
 
-inline bool
-TokenKindIsDecl(TokenKind tt)
-{
-    return tt == TOK_VAR || tt == TOK_LET;
-}
-
 } // namespace frontend
 } // namespace js
 
 #endif /* frontend_TokenKind_h */
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -976,21 +976,16 @@ bool
 TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
 {
     if (kw->tokentype == TOK_RESERVED)
         return reportError(JSMSG_RESERVED_ID, kw->chars);
 
     if (kw->tokentype == TOK_STRICT_RESERVED)
         return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
 
-    // Treat 'let' as an identifier and contextually a keyword in sloppy mode.
-    // It is always a keyword in strict mode.
-    if (kw->tokentype == TOK_LET && !strictMode())
-        return true;
-
     // Working keyword.
     if (ttp) {
         *ttp = kw->tokentype;
         return true;
     }
 
     return reportError(JSMSG_RESERVED_ID, kw->chars);
 }
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -346,16 +346,23 @@ class MOZ_STACK_CLASS TokenStream
 
     PropertyName* nextName() const {
         if (nextToken().type == TOK_YIELD)
             return cx->names().yield;
         MOZ_ASSERT(nextToken().type == TOK_NAME);
         return nextToken().name();
     }
 
+    bool nextNameContainsEscape() const {
+        if (nextToken().type == TOK_YIELD)
+            return false;
+        MOZ_ASSERT(nextToken().type == TOK_NAME);
+        return nextToken().nameContainsEscape();
+    }
+
     bool isCurrentTokenAssignment() const {
         return TokenKindIsAssignment(currentToken().type);
     }
 
     // Flag methods.
     bool isEOF() const { return flags.isEOF; }
     bool sawOctalEscape() const { return flags.sawOctalEscape; }
     bool hadError() const { return flags.hadError; }
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2524,21 +2524,24 @@ js::TenuringTracer::moveObjectToTenured(
     if (src->is<InlineTypedObject>()) {
         InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
     } else if (src->is<TypedArrayObject>()) {
         tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
     } else if (src->is<UnboxedArrayObject>()) {
         tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
     } else if (src->is<ArgumentsObject>()) {
         tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
+    } else if (src->is<ProxyObject>()) {
+        tenuredSize += ProxyObject::objectMovedDuringMinorGC(this, dst, src);
     } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) {
         op(dst, src);
-    } else if (src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE) {
-        // Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
-        // to ensure any additional nursery buffers they hold are moved.
+    } else if (src->getClass()->hasFinalize()) {
+        // Such objects need to be handled specially above to ensure any
+        // additional nursery buffers they hold are moved.
+        MOZ_RELEASE_ASSERT(CanNurseryAllocateFinalizedClass(src->getClass()));
         MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class");
     }
 
     return tenuredSize;
 }
 
 size_t
 js::TenuringTracer::moveSlotsToTenured(NativeObject* dst, NativeObject* src, AllocKind dstKind)
@@ -2800,40 +2803,16 @@ FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL
 FOR_EACH_PUBLIC_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_HEAP_TRACE_FUNCTIONS)
 FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_HEAP_TRACE_FUNCTIONS)
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
 } /* namespace gc */
 } /* namespace js */
 
 
-/*** Type Marking *********************************************************************************/
-
-void
-TypeSet::MarkTypeRoot(JSTracer* trc, TypeSet::Type* v, const char* name)
-{
-    AssertRootMarkingPhase(trc);
-    MarkTypeUnbarriered(trc, v, name);
-}
-
-void
-TypeSet::MarkTypeUnbarriered(JSTracer* trc, TypeSet::Type* v, const char* name)
-{
-    if (v->isSingletonUnchecked()) {
-        JSObject* obj = v->singletonNoBarrier();
-        DispatchToTracer(trc, &obj, name);
-        *v = TypeSet::ObjectType(obj);
-    } else if (v->isGroupUnchecked()) {
-        ObjectGroup* group = v->groupNoBarrier();
-        DispatchToTracer(trc, &group, name);
-        *v = TypeSet::ObjectType(group);
-    }
-}
-
-
 /*** Cycle Collector Barrier Implementation *******************************************************/
 
 #ifdef DEBUG
 struct AssertNonGrayTracer : public JS::CallbackTracer {
     explicit AssertNonGrayTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {}
     void onChild(const JS::GCCellPtr& thing) override {
         MOZ_ASSERT_IF(thing.asCell()->isTenured(),
                       !thing.asCell()->asTenured().isMarked(js::gc::GRAY));
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -249,33 +249,29 @@ js::Nursery::leaveZealMode() {
 #endif // JS_GC_ZEAL
 
 JSObject*
 js::Nursery::allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp)
 {
     /* Ensure there's enough space to replace the contents with a RelocationOverlay. */
     MOZ_ASSERT(size >= sizeof(RelocationOverlay));
 
-    /*
-     * Classes with JSCLASS_SKIP_NURSERY_FINALIZE will not have their finalizer
-     * called if they are nursery allocated and not promoted to the tenured
-     * heap. The finalizers for these classes must do nothing except free data
-     * which was allocated via Nursery::allocateBuffer.
-     */
-    MOZ_ASSERT_IF(clasp->hasFinalize(), clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE);
+    /* Sanity check the finalizer. */
+    MOZ_ASSERT_IF(clasp->hasFinalize(), CanNurseryAllocateFinalizedClass(clasp) ||
+                                        clasp->isProxy());
 
     /* Make the object allocation. */
     JSObject* obj = static_cast<JSObject*>(allocate(size));
     if (!obj)
         return nullptr;
 
     /* If we want external slots, add them. */
     HeapSlot* slots = nullptr;
     if (numDynamic) {
-        MOZ_ASSERT(clasp->isNative());
+        MOZ_ASSERT(clasp->isNative() || clasp->isProxy());
         slots = static_cast<HeapSlot*>(allocateBuffer(cx->zone(), numDynamic * sizeof(HeapSlot)));
         if (!slots) {
             /*
              * It is safe to leave the allocated object uninitialized, since we
              * do not visit unallocated things in the nursery.
              */
             return nullptr;
         }
@@ -695,17 +691,17 @@ js::Nursery::doCollection(JSRuntime* rt,
     // objects are left to move. That is, we iterate to a fixed point.
     maybeStartProfile(ProfileKey::CollectToFP);
     collectToFixedPoint(mover, tenureCounts);
     maybeEndProfile(ProfileKey::CollectToFP);
 
     // Sweep compartments to update the array buffer object's view lists.
     maybeStartProfile(ProfileKey::SweepArrayBufferViewList);
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
-        c->sweepAfterMinorGC();
+        c->sweepAfterMinorGC(&mover);
     maybeEndProfile(ProfileKey::SweepArrayBufferViewList);
 
     // Update any slot or element pointers whose destination has been tenured.
     maybeStartProfile(ProfileKey::UpdateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC(rt, &mover);
     forwardedBuffers.finish();
     maybeEndProfile(ProfileKey::UpdateJitActivations);
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -107,16 +107,30 @@ class TenuringTracer : public JSTracer
     JSObject* moveToTenured(JSObject* src);
     size_t moveObjectToTenured(JSObject* dst, JSObject* src, gc::AllocKind dstKind);
     size_t moveElementsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
     size_t moveSlotsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
 
     void traceSlots(JS::Value* vp, JS::Value* end);
 };
 
+/*
+ * Classes with JSCLASS_SKIP_NURSERY_FINALIZE or Wrapper classes with
+ * CROSS_COMPARTMENT flags will not have their finalizer called if they are
+ * nursery allocated and not promoted to the tenured heap. The finalizers for
+ * these classes must do nothing except free data which was allocated via
+ * Nursery::allocateBuffer.
+ */
+inline bool
+CanNurseryAllocateFinalizedClass(const js::Class* const clasp)
+{
+    MOZ_ASSERT(clasp->hasFinalize());
+    return clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE;
+}
+
 class Nursery
 {
   public:
     static const size_t Alignment = gc::ChunkSize;
     static const size_t ChunkShift = gc::ChunkShift;
 
     explicit Nursery(JSRuntime* rt);
     ~Nursery();
new file mode 100644
--- /dev/null
+++ b/js/src/gc/NurseryAwareHashMap.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef gc_NurseryAwareHashMap_h
+#define gc_NurseryAwareHashMap_h
+
+namespace js {
+
+namespace detail {
+// This class only handles the incremental case and does not deal with nursery
+// pointers. The only users should be for NurseryAwareHashMap; it is defined
+// externally because we need a GCPolicy for its use in the contained map.
+template <typename T>
+class UnsafeBareReadBarriered : public ReadBarrieredBase<T>
+{
+  public:
+    UnsafeBareReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    MOZ_IMPLICIT UnsafeBareReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {}
+    explicit UnsafeBareReadBarriered(const UnsafeBareReadBarriered& v) : ReadBarrieredBase<T>(v) {}
+    UnsafeBareReadBarriered(UnsafeBareReadBarriered&& v)
+      : ReadBarrieredBase<T>(mozilla::Forward<UnsafeBareReadBarriered<T>>(v))
+    {}
+
+    UnsafeBareReadBarriered& operator=(const UnsafeBareReadBarriered& v) {
+        this->value = v.value;
+        return *this;
+    }
+
+    UnsafeBareReadBarriered& operator=(const T& v) {
+        this->value = v;
+        return *this;
+    }
+
+    const T get() const {
+        if (!InternalBarrierMethods<T>::isMarkable(this->value))
+            return JS::GCPolicy<T>::initial();
+        this->read();
+        return this->value;
+    }
+
+    explicit operator bool() const {
+        return bool(this->value);
+    }
+
+    const T unbarrieredGet() const { return this->value; }
+    T* unsafeGet() { return &this->value; }
+    T const* unsafeGet() const { return &this->value; }
+};
+} // namespace detail
+
+// The "nursery aware" hash map is a special case of GCHashMap that is able to
+// treat nursery allocated members weakly during a minor GC: e.g. it allows for
+// nursery allocated objects to be collected during nursery GC where a normal
+// hash table treats such edges strongly.
+//
+// Doing this requires some strong constraints on what can be stored in this
+// table and how it can be accessed. At the moment, this table assumes that
+// all values contain a strong reference to the key. It also requires the
+// policy to contain an |isTenured| and |needsSweep| members, which is fairly
+// non-standard. This limits its usefulness to the CrossCompartmentMap at the
+// moment, but might serve as a useful base for other tables in future.
+template <typename Key,
+          typename Value,
+          typename HashPolicy = DefaultHasher<Key>,
+          typename AllocPolicy = TempAllocPolicy>
+class NurseryAwareHashMap
+{
+    using BarrieredValue = detail::UnsafeBareReadBarriered<Value>;
+    using MapType = GCRekeyableHashMap<Key, BarrieredValue, HashPolicy, AllocPolicy>;
+    MapType map;
+
+    // Keep a list of all keys for which JS::GCPolicy<Key>::isTenured is false.
+    // This lets us avoid a full traveral of the map on each minor GC, keeping
+    // the minor GC times proportional to the nursery heap size.
+    Vector<Key, 0, AllocPolicy> nurseryEntries;
+
+  public:
+    using Lookup = typename MapType::Lookup;
+    using Ptr = typename MapType::Ptr;
+    using Range = typename MapType::Range;
+
+    explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy()) : map(a) {}
+
+    MOZ_MUST_USE bool init(uint32_t len = 16) { return map.init(len); }
+
+    bool empty() const { return map.empty(); }
+    Ptr lookup(const Lookup& l) const { return map.lookup(l); }
+    void remove(Ptr p) { map.remove(p); }
+    Range all() const { return map.all(); }
+    struct Enum : public MapType::Enum {
+        explicit Enum(NurseryAwareHashMap& namap) : MapType::Enum(namap.map) {}
+    };
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return map.sizeOfExcludingThis(mallocSizeOf);
+    }
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return map.sizeOfIncludingThis(mallocSizeOf);
+    }
+
+    MOZ_MUST_USE bool put(const Key& k, const Value& v) {
+        auto p = map.lookupForAdd(k);
+        if (p) {
+            if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
+                if (!nurseryEntries.append(k))
+                    return false;
+            }
+            p->value() = v;
+            return true;
+        }
+
+        bool ok = map.add(p, k, v);
+        if (!ok)
+            return false;
+
+        if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
+            if (!nurseryEntries.append(k)) {
+                map.remove(k);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    void sweepAfterMinorGC(JSTracer* trc) {
+        for (auto& key : nurseryEntries) {
+            auto p = map.lookup(key);
+            if (!p)
+                continue;
+
+            // Drop the entry if the value is not marked.
+            if (JS::GCPolicy<BarrieredValue>::needsSweep(&p->value())) {
+                map.remove(key);
+                continue;
+            }
+
+            // Update and relocate the key, if the value is still needed.
+            //
+            // Note that this currently assumes that all Value will contain a
+            // strong reference to Key, as per its use as the
+            // CrossCompartmentWrapperMap. We may need to make the following
+            // behavior more dynamic if we use this map in other nursery-aware
+            // contexts.
+            Key copy(key);
+            mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(&copy);
+            MOZ_ASSERT(!sweepKey);
+            map.rekeyIfMoved(key, copy);
+        }
+        nurseryEntries.clear();
+    }
+
+    void sweep() {
+        MOZ_ASSERT(nurseryEntries.empty());
+        map.sweep();
+    }
+};
+
+} // namespace js
+
+namespace JS {
+template <typename T>
+struct GCPolicy<js::detail::UnsafeBareReadBarriered<T>>
+{
+    static void trace(JSTracer* trc, js::detail::UnsafeBareReadBarriered<T>* thingp,
+                      const char* name)
+    {
+        js::TraceEdge(trc, thingp, name);
+    }
+    static bool needsSweep(js::detail::UnsafeBareReadBarriered<T>* thingp) {
+        return js::gc::IsAboutToBeFinalized(thingp);
+    }
+};
+} // namespace JS
+
+#endif // gc_NurseryAwareHashMap_h
--- a/js/src/jit-test/tests/basic/destructuring-default.js
+++ b/js/src/jit-test/tests/basic/destructuring-default.js
@@ -148,42 +148,107 @@ var evaled = false;
 ({a: {} = (evaled = true, {})} = {});
 assertEq(evaled, true);
 evaled = false;
 assertThrowsInstanceOf(() => { [[] = (evaled = true, 2)] = [] }, TypeError);
 assertEq(evaled, true);
 
 assertThrowsInstanceOf(() => new Function('var [...rest = defaults] = [];'), SyntaxError);
 
-// bug 1124480: destructuring defaults should work correctly as part of for-in
-var defaultsSupportedInForVar = false;
-try
-{
-  new Function('for (var [a, b = 10] in " ") {}');
-  defaultsSupportedInForVar = true;
-}
-catch (e)
-{
-}
+new Function(`
+  b = undefined;
+  for (var [a, b = 10] in " ") {}
+  assertEq(b, 10);`)();
+
+new Function(`
+  b = undefined;
+  for (let [a, c = 10] in " ") { b = c; }
+  assertEq(b, 10);`)();
+
+new Function(`
+  b = undefined;
+  for (let [a, c = (x => y)] in " ") { b = c; }
+  assertEq(typeof b, "function");`)();
+
+new Function(`
+  b = undefined;
+  for (let [a, __proto__ = 10] in " ") { b = __proto__; }
+  assertEq(b, 10);`)();
 
-if (defaultsSupportedInForVar) {
-  new Function(`
-    b = undefined;
-    for (var [a, b = 10] in " ") {}
-    assertEq(b, 10);
+new Function(`
+  b = undefined;
+  for (let [a, __proto__ = (x => y)] in " ") { b = __proto__; }
+  assertEq(typeof b, "function");`)();
+
+new Function(`
+  b = undefined;
+  for (var {1: b = 10} in " ") {}
+  assertEq(b, 10);`)();
+
+new Function(`
+  b = undefined;
+  for (let {1: c = 10} in " ") { b = c; }
+  assertEq(b, 10);`)();
+
+new Function(`
+  b = undefined;
+  for (let { c = 10 } in " ") { b = c; }
+  assertEq(b, 10);`)();
+
+new Function(`
+  b = undefined;
+  assertEq(Number.prototype.a, undefined);
+  for (var { a: c = (x => y) } in [{}]) { b = c; }
+  assertEq(typeof b, "function");`)();
 
-    b = undefined;
-    for (let [a, c = 10] in " ") { b = c; }
-    assertEq(b, 10);
+new Function(`
+  b = undefined;
+  Object.defineProperty(String.prototype, "__proto__",
+                        { value: undefined, configurable: true });
+  for (var { __proto__: c = (x => y) } in [{}]) { b = c; }
+  delete String.prototype.__proto__;
+  assertEq(typeof b, "function");`)();
 
-    b = undefined;
-    for (var {1: b = 10} in " ") {}
-    assertEq(b, 10);
+new Function(`
+  b = undefined;
+  for (var { a: c = (x => y) } of [{ a: undefined }]) { b = c; }
+  assertEq(typeof b, "function");`)();
+
+new Function(`
+  b = undefined;
+  for (var { __proto__: c = (x => y) } of [{ ["__proto__"]: undefined }]) { b = c; }
+  assertEq(typeof b, "function");`)();
 
-    b = undefined;
-    for (let {1: c = 10} in " ") { b = c; }
-    assertEq(b, 10);
+new Function(`
+  b = undefined;
+  var ts = Function.prototype.toString;
+  Function.prototype.toString = () => 'hi';
+  String.prototype.hi = 42;
+  for (var { [(x => y)]: c } in [0]) { b = c; }
+  Function.prototype.toString = ts;
+  delete String.prototype.hi;
+  assertEq(b, 42);`)();
 
-    b = undefined;
-    for (let {c = 10} in " ") { b = c; }
-    assertEq(b, 10);
-  `)();
-}
+new Function(`
+  b = undefined;
+  var ts = Function.prototype.toString;
+  Function.prototype.toString = () => 'hi';
+  String.prototype.hi = 42;
+  for (var { [(x => y)]: __proto__ } in [0]) { b = __proto__; }
+  Function.prototype.toString = ts;
+  delete String.prototype.hi;
+  assertEq(b, 42);`)();
+
+new Function(`
+  b = undefined;
+  var ts = Function.prototype.toString;
+  Function.prototype.toString = () => 'hi';
+  for (var { [(x => y)]: c } of [{ 'hi': 42 }]) { b = c; }
+  Function.prototype.toString = ts;
+  assertEq(b, 42);`)();
+
+new Function(`
+  b = undefined;
+  var ts = Function.prototype.toString;
+  Function.prototype.toString = () => 'hi';
+  for (var { [(x => y)]: __proto__ } of [{ hi: 42 }]) { b = __proto__; }
+  Function.prototype.toString = ts;
+  assertEq(b, 42);`)();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/promise/bug-1298776.js
@@ -0,0 +1,2 @@
+if (typeof oomTest === 'function')
+    oomTest(Function(`new Promise(res=>res)`));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/regexp/clone-statics.js
@@ -0,0 +1,21 @@
+if (helperThreadCount() === 0)
+  quit(0);
+
+offThreadCompileScript(`
+  function foo(x, {}) {
+    do {
+      re = /erwe/;
+      if (x === 1)
+        re.x = 1;
+      else
+        re.x = "a";
+      assertEq(re.x.length, (x === 1) ? undefined : 1);
+    } while (!inIon());
+  }
+
+  foo(0, 0);
+  RegExp.multiline = 1;
+  foo(1, 0);
+`);
+
+runOffThreadScript();
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -362,38 +362,38 @@ var {v2i, i2i, i2v} = wasmEvalText(`(mod
     (func (param i32) (result i32) (call_indirect 0 (get_local 0)))
     (func (param i32) (param i32) (result i32) (call_indirect 1 (get_local 0) (get_local 1)))
     (func (param i32) (call_indirect 2 (get_local 0) (i32.const 0)))
     (export "v2i" 6)
     (export "i2i" 7)
     (export "i2v" 8)
 )`);
 
-const badIndirectCall = /bad wasm indirect call/;
+const signatureMismatch = /indirect call signature mismatch/;
 
 assertEq(v2i(0), 13);
 assertEq(v2i(1), 42);
-assertErrorMessage(() => v2i(2), Error, badIndirectCall);
-assertErrorMessage(() => v2i(3), Error, badIndirectCall);
-assertErrorMessage(() => v2i(4), Error, badIndirectCall);
-assertErrorMessage(() => v2i(5), Error, badIndirectCall);
+assertErrorMessage(() => v2i(2), Error, signatureMismatch);
+assertErrorMessage(() => v2i(3), Error, signatureMismatch);
+assertErrorMessage(() => v2i(4), Error, signatureMismatch);
+assertErrorMessage(() => v2i(5), Error, signatureMismatch);
 
-assertErrorMessage(() => i2i(0), Error, badIndirectCall);
-assertErrorMessage(() => i2i(1), Error, badIndirectCall);
+assertErrorMessage(() => i2i(0), Error, signatureMismatch);
+assertErrorMessage(() => i2i(1), Error, signatureMismatch);
 assertEq(i2i(2, 100), 101);
 assertEq(i2i(3, 100), 102);
 assertEq(i2i(4, 100), 103);
 assertEq(i2i(5, 100), 104);
 
-assertErrorMessage(() => i2v(0), Error, badIndirectCall);
-assertErrorMessage(() => i2v(1), Error, badIndirectCall);
-assertErrorMessage(() => i2v(2), Error, badIndirectCall);
-assertErrorMessage(() => i2v(3), Error, badIndirectCall);
-assertErrorMessage(() => i2v(4), Error, badIndirectCall);
-assertErrorMessage(() => i2v(5), Error, badIndirectCall);
+assertErrorMessage(() => i2v(0), Error, signatureMismatch);
+assertErrorMessage(() => i2v(1), Error, signatureMismatch);
+assertErrorMessage(() => i2v(2), Error, signatureMismatch);
+assertErrorMessage(() => i2v(3), Error, signatureMismatch);
+assertErrorMessage(() => i2v(4), Error, signatureMismatch);
+assertErrorMessage(() => i2v(5), Error, signatureMismatch);
 
 {
     enableSPSProfiling();
 
     var stack;
     assertEq(wasmEvalText(
         `(module
             (type $v2v (func))
--- a/js/src/jit-test/tests/wasm/table-gc.js
+++ b/js/src/jit-test/tests/wasm/table-gc.js
@@ -17,17 +17,17 @@ var callee = i => `(func $f${i} (type $v
 // hold instances alive and instances hold imported tables alive. Nothing
 // should hold the export object alive.
 resetFinalizeCount();
 var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 var f = t.get(0);
 assertEq(f(), e.call(0));
-assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/);
+assertErrorMessage(() => e.call(1), Error, /indirect call to null/);
 assertErrorMessage(() => e.call(2), Error, /out-of-range/);
 assertEq(finalizeCount(), 0);
 i.edge = makeFinalizeObserver();
 e.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 f.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 0);
@@ -74,34 +74,33 @@ gc();
 assertEq(finalizeCount(), 2);
 i = null;
 gc();
 assertEq(finalizeCount(), 2);
 t = null;
 gc();
 assertEq(finalizeCount(), 4);
 
-// The bad-indirect-call stub should (currently, could be changed later) keep
-// the instance containing that stub alive.
+// Null elements shouldn't keep anything alive.
 resetFinalizeCount();
 var i = evalText(`(module (table (resizable 2)) (export "tbl" table) ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 i.edge = makeFinalizeObserver();
 e.edge = makeFinalizeObserver();
 t.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 0);
 i.exports = null;
 e = null;
 gc();
 assertEq(finalizeCount(), 1);
 i = null;
 gc();
-assertEq(finalizeCount(), 1);
+assertEq(finalizeCount(), 2);
 t = null;
 gc();
 assertEq(finalizeCount(), 3);
 
 // Before initialization, a table is not bound to any instance.
 resetFinalizeCount();
 var i = evalText(`(module (func $f0 (result i32) (i32.const 0)) (export "f0" $f0))`);
 var t = new Table({initial:4, element:"anyfunc"});
@@ -139,24 +138,23 @@ t.get(0).edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 2);
 i = null;
 gc();
 assertEq(finalizeCount(), 2);
 t.set(0, null);
 assertEq(t.get(0), null);
 gc();
-assertEq(finalizeCount(), 2);
+assertEq(finalizeCount(), 3);
 t = null;
 gc();
 assertEq(finalizeCount(), 4);
 
-// Once all of an instance's elements in a Table (including the
-// bad-indirect-call stub) have been clobbered, the Instance should not be
-// rooted.
+// Once all of an instance's elements in a Table have been clobbered, the
+// Instance should not be reachable.
 resetFinalizeCount();
 var i1 = evalText(`(module (func $f1 (result i32) (i32.const 13)) (export "f1" $f1))`);
 var i2 = evalText(`(module (func $f2 (result i32) (i32.const 42)) (export "f2" $f2))`);
 var f1 = i1.exports.f1;
 var f2 = i2.exports.f2;
 var t = new Table({initial:2, element:"anyfunc"});
 i1.edge = makeFinalizeObserver();
 i2.edge = makeFinalizeObserver();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/table-pre-barrier.js
@@ -0,0 +1,24 @@
+load(libdir + "wasm.js");
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Table = WebAssembly.Table;
+
+var i42 = new Instance(new Module(textToBinary(`(module (func (result i32) (i32.const 42)) (export "f" 0))`)));
+var i13 = new Instance(new Module(textToBinary(`(module (func (result i32) (i32.const 13)) (export "f" 0))`)));
+var t = new Table({element:"anyfunc", initial:1});
+t.set(0, i42.exports.f);
+
+assertEq(t.get(0)(), 42);
+
+verifyprebarriers();
+t.set(0, i13.exports.f);
+verifyprebarriers();
+
+assertEq(t.get(0)(), 13);
+
+verifyprebarriers();
+t.set(0, null);
+verifyprebarriers();
+
+assertEq(t.get(0), null);
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -35,46 +35,46 @@ var m = new Module(textToBinary(`
 var tbl = new Table({initial:50, element:"anyfunc"});
 assertEq(new Instance(m, {globals:{a:20, table:tbl}}) instanceof Instance, true);
 assertErrorMessage(() => new Instance(m, {globals:{a:50, table:tbl}}), RangeError, /elem segment does not fit/);
 
 var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
 var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
 
 var call = evalText(`(module (table (resizable 10)) ${callee(0)} ${caller})`).exports.call;
-assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(0), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
 var call = evalText(`(module (table (resizable 10)) (elem (i32.const 0)) ${callee(0)} ${caller})`).exports.call;
-assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(0), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
 var call = evalText(`(module (table (resizable 10)) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`).exports.call;
 assertEq(call(0), 0);
-assertErrorMessage(() => call(1), Error, /bad wasm indirect call/);
-assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(1), Error, /indirect call to null/);
+assertErrorMessage(() => call(2), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
 var call = evalText(`(module (table (resizable 10)) (elem (i32.const 1) $f0 $f1) (elem (i32.const 4) $f0 $f2) ${callee(0)} ${callee(1)} ${callee(2)} ${caller})`).exports.call;
-assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(0), Error, /indirect call to null/);
 assertEq(call(1), 0);
 assertEq(call(2), 1);
-assertErrorMessage(() => call(3), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(3), Error, /indirect call to null/);
 assertEq(call(4), 0);
 assertEq(call(5), 2);
-assertErrorMessage(() => call(6), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(6), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
 var tbl = new Table({initial:3, element:"anyfunc"});
 var call = evalText(`(module (import "a" "b" (table 3)) (export "tbl" table) (elem (i32.const 0) $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call;
 assertEq(call(0), 0);
 assertEq(call(1), 1);
 assertEq(tbl.get(0)(), 0);
 assertEq(tbl.get(1)(), 1);
-assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(2), Error, /indirect call to null/);
 assertEq(tbl.get(2), null);
 
 var exp = evalText(`(module (import "a" "b" (table 3)) (export "tbl" table) (elem (i32.const 2) $f2) ${callee(2)} ${caller})`, {a:{b:tbl}}).exports;
 assertEq(exp.tbl, tbl);
 assertEq(exp.call(0), 0);
 assertEq(exp.call(1), 1);
 assertEq(exp.call(2), 2);
 assertEq(call(0), 0);
@@ -85,17 +85,17 @@ assertEq(tbl.get(1)(), 1);
 assertEq(tbl.get(2)(), 2);
 
 var exp1 = evalText(`(module (table (resizable 10)) (export "tbl" table) (elem (i32.const 0) $f0 $f0) ${callee(0)} (export "f0" $f0) ${caller})`).exports
 assertEq(exp1.tbl.get(0), exp1.f0);
 assertEq(exp1.tbl.get(1), exp1.f0);
 assertEq(exp1.tbl.get(2), null);
 assertEq(exp1.call(0), 0);
 assertEq(exp1.call(1), 0);
-assertErrorMessage(() => exp1.call(2), Error, /bad wasm indirect call/);
+assertErrorMessage(() => exp1.call(2), Error, /indirect call to null/);
 var exp2 = evalText(`(module (import "a" "b" (table 10)) (export "tbl" table) (elem (i32.const 1) $f1 $f1) ${callee(1)} (export "f1" $f1) ${caller})`, {a:{b:exp1.tbl}}).exports
 assertEq(exp1.tbl, exp2.tbl);
 assertEq(exp2.tbl.get(0), exp1.f0);
 assertEq(exp2.tbl.get(1), exp2.f1);
 assertEq(exp2.tbl.get(2), exp2.f1);
 assertEq(exp1.call(0), 0);
 assertEq(exp1.call(1), 1);
 assertEq(exp1.call(2), 1);
@@ -107,17 +107,17 @@ var tbl = new Table({initial:3, element:
 var e1 = evalText(`(module (func $f (result i32) (i32.const 42)) (export "f" $f))`).exports;
 var e2 = evalText(`(module (func $g (result f32) (f32.const 10)) (export "g" $g))`).exports;
 var e3 = evalText(`(module (func $h (result i32) (i32.const 13)) (export "h" $h))`).exports;
 tbl.set(0, e1.f);
 tbl.set(1, e2.g);
 tbl.set(2, e3.h);
 var e4 = evalText(`(module (import "a" "b" (table 3)) ${caller})`, {a:{b:tbl}}).exports;
 assertEq(e4.call(0), 42);
-assertErrorMessage(() => e4.call(1), Error, /bad wasm indirect call/);
+assertErrorMessage(() => e4.call(1), Error, /indirect call signature mismatch/);
 assertEq(e4.call(2), 13);
 
 var m = new Module(textToBinary(`(module
     (type $i2i (func (param i32) (result i32)))
     (import "a" "mem" (memory 1))
     (import "a" "tbl" (table 10))
     (import $imp "a" "imp" (result i32))
     (func $call (param $i i32) (result i32)
@@ -159,17 +159,17 @@ var call = evalText(`(module
     (func $a (type $v2i1) (result i32) (i32.const 0))
     (func $b (type $v2i2) (result i32) (i32.const 1))
     (func $c (type $i2v) (param i32))
     (func $call (param i32) (result i32) (call_indirect $v2i1 (get_local 0)))
     (export "call" $call)
 )`).exports.call;
 assertEq(call(0), 0);
 assertEq(call(1), 1);
-assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
+assertErrorMessage(() => call(2), Error, /indirect call signature mismatch/);
 
 var call = evalText(`(module
     (type $A (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
     (type $B (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
     (type $C (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
     (type $D (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
     (type $E (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
     (type $F (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
@@ -183,10 +183,10 @@ var call = evalText(`(module
     (func $f (type $F) (get_local 12))
     (func $g (type $G) (get_local 13))
     (func $call (param i32) (result i32)
         (call_indirect $A (get_local 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 42)))
     (export "call" $call)
 )`).exports.call;
 assertEq(call(0), 42);
 for (var i = 1; i < 7; i++)
-    assertErrorMessage(() => call(i), Error, /bad wasm indirect call/);
+    assertErrorMessage(() => call(i), Error, /indirect call signature mismatch/);
 assertErrorMessage(() => call(7), Error, /out-of-range/);
--- a/js/src/jit/JitcodeMap.cpp
+++ b/js/src/jit/JitcodeMap.cpp
@@ -19,16 +19,18 @@
 #include "gc/Statistics.h"
 #include "jit/BaselineJIT.h"
 #include "jit/JitSpewer.h"
 #include "js/Vector.h"
 #include "vm/SPSProfiler.h"
 
 #include "jsscriptinlines.h"
 
+#include "vm/TypeInference-inl.h"
+
 using mozilla::Maybe;
 
 namespace js {
 namespace jit {
 
 static inline JitcodeRegionEntry
 RegionAtAddr(const JitcodeGlobalEntry::IonEntry& entry, void* ptr,
              uint32_t* ptrOffset)
@@ -893,17 +895,17 @@ JitcodeGlobalEntry::IonEntry::mark(JSTra
 
     if (!optsAllTypes_)
         return markedAny;
 
     for (IonTrackedTypeWithAddendum* iter = optsAllTypes_->begin();
          iter != optsAllTypes_->end(); iter++)
     {
         if (ShouldMarkProvider::ShouldMark(&iter->type)) {
-            TypeSet::MarkTypeUnbarriered(trc, &iter->type, "jitcodeglobaltable-ionentry-type");
+            iter->type.trace(trc);
             markedAny = true;
         }
         if (iter->hasAllocationSite() && ShouldMarkProvider::ShouldMark(&iter->script)) {
             TraceManuallyBarrieredEdge(trc, &iter->script,
                                        "jitcodeglobaltable-ionentry-type-addendum-script");
             markedAny = true;
         } else if (iter->hasConstructor() && ShouldMarkProvider::ShouldMark(&iter->constructor)) {
             TraceManuallyBarrieredEdge(trc, &iter->constructor,
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -2813,21 +2813,24 @@ MacroAssembler::wasmCallIndirect(const w
         if (sizeof(wasm::ExternalTableElem) == 8) {
             computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
         } else {
             lshift32(Imm32(4), index);
             addPtr(index, scratch);
         }
 
         loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, tls)), WasmTlsReg);
+        branchTest32(Assembler::Zero, WasmTlsReg, WasmTlsReg, wasm::JumpTarget::IndirectCallToNull);
+
         loadWasmPinnedRegsFromTls();
 
         loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, code)), scratch);
     } else {
         loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
+        branchTest32(Assembler::Zero, scratch, scratch, wasm::JumpTarget::IndirectCallToNull);
     }
 
     call(desc, scratch);
 }
 
 //}}} check_macroassembler_style
 
 void
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -280,16 +280,17 @@ CodeGeneratorX86::visitLoadTypedArrayEle
             masm.j(Assembler::AboveOrEqual, ool->entry());
         else
             bailoutIf(Assembler::AboveOrEqual, ins->snapshot());
     }
 
     Operand srcAddr(ptr, int32_t(mir->base().asValue()) + int32_t(offset));
     switch (accessType) {
       case Scalar::Int8:         masm.movsblWithPatch(srcAddr, out.gpr()); break;
+      case Scalar::Uint8Clamped:
       case Scalar::Uint8:        masm.movzblWithPatch(srcAddr, out.gpr()); break;
       case Scalar::Int16:        masm.movswlWithPatch(srcAddr, out.gpr()); break;
       case Scalar::Uint16:       masm.movzwlWithPatch(srcAddr, out.gpr()); break;
       case Scalar::Int32:
       case Scalar::Uint32:       masm.movlWithPatch(srcAddr, out.gpr()); break;
       case Scalar::Float32:      masm.vmovssWithPatch(srcAddr, out.fpu()); break;
       case Scalar::Float64:      masm.vmovsdWithPatch(srcAddr, out.fpu()); break;
       default:                   MOZ_CRASH("Unexpected type");
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -199,17 +199,17 @@ MSG_DEF(JSMSG_LET_STARTING_FOROF_LHS,  0
 MSG_DEF(JSMSG_BAD_GENERATOR_RETURN,    1, JSEXN_TYPEERR,   "generator function {0} returns a value")
 MSG_DEF(JSMSG_BAD_GENEXP_BODY,         1, JSEXN_SYNTAXERR, "illegal use of {0} in generator expression")
 MSG_DEF(JSMSG_BAD_INCOP_OPERAND,       0, JSEXN_REFERENCEERR, "invalid increment/decrement operand")
 MSG_DEF(JSMSG_BAD_METHOD_DEF,          0, JSEXN_SYNTAXERR, "bad method definition")
 MSG_DEF(JSMSG_BAD_OCTAL,               1, JSEXN_SYNTAXERR, "{0} is not a legal ECMA-262 octal constant")
 MSG_DEF(JSMSG_BAD_OPERAND,             1, JSEXN_SYNTAXERR, "invalid {0} operand")
 MSG_DEF(JSMSG_BAD_PROP_ID,             0, JSEXN_SYNTAXERR, "invalid property id")
 MSG_DEF(JSMSG_BAD_RETURN_OR_YIELD,     1, JSEXN_SYNTAXERR, "{0} not in function")
-MSG_DEF(JSMSG_BAD_STRICT_ASSIGN,       1, JSEXN_SYNTAXERR, "can't assign to {0} in strict mode")
+MSG_DEF(JSMSG_BAD_STRICT_ASSIGN,       1, JSEXN_SYNTAXERR, "'{0}' can't be defined or assigned to in strict mode code")
 MSG_DEF(JSMSG_BAD_SWITCH,              0, JSEXN_SYNTAXERR, "invalid switch statement")
 MSG_DEF(JSMSG_BAD_SUPER,               0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'")
 MSG_DEF(JSMSG_BAD_SUPERPROP,           1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods")
 MSG_DEF(JSMSG_BAD_SUPERCALL,           0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors")
 MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension")
 MSG_DEF(JSMSG_BRACKET_AFTER_LIST,      0, JSEXN_SYNTAXERR, "missing ] after element list")
 MSG_DEF(JSMSG_BRACKET_IN_INDEX,        0, JSEXN_SYNTAXERR, "missing ] in index expression")
 MSG_DEF(JSMSG_CATCH_AFTER_GENERAL,     0, JSEXN_SYNTAXERR, "catch after unconditional catch")
@@ -343,17 +343,18 @@ MSG_DEF(JSMSG_ESCAPED_KEYWORD,         0
 MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL,       1, JSEXN_TYPEERR, "asm.js type error: {0}")
 MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,       1, JSEXN_TYPEERR, "asm.js link error: {0}")
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,         1, JSEXN_WARN,    "Successfully compiled asm.js code ({0})")
 
 // wasm
 MSG_DEF(JSMSG_WASM_FAIL,               1, JSEXN_TYPEERR,     "wasm error: {0}")
 MSG_DEF(JSMSG_WASM_DECODE_FAIL,        2, JSEXN_TYPEERR,     "wasm validation error at offset {0}: {1}")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
-MSG_DEF(JSMSG_WASM_BAD_IND_CALL,       0, JSEXN_ERR,         "bad wasm indirect call")
+MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL,   0, JSEXN_ERR,         "indirect call to null")
+MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG,   0, JSEXN_ERR,         "indirect call signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_GROW,           0, JSEXN_ERR,         "failed to grow memory")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_TYPEERR,     "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_SIZE,           2, JSEXN_TYPEERR,     "bad {0} {1} size")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument, if present, must be an object")
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -1,21 +1,80 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi.h"
+#include "jscompartment.h"
 
 #include "js/RootingAPI.h"
 #include "js/SliceBudget.h"
 #include "jsapi-tests/tests.h"
 
+static bool
+ConstructCCW(JSContext* cx, const JSClass* globalClasp,
+             JS::HandleObject global1, JS::MutableHandleObject wrapper,
+             JS::MutableHandleObject global2, JS::MutableHandleObject wrappee)
+{
+    if (!global1) {
+        fprintf(stderr, "null initial global");
+        return false;
+    }
+
+    // Define a second global in a different zone.
+    JS::CompartmentOptions options;
+    global2.set(JS_NewGlobalObject(cx, globalClasp, nullptr,
+                                   JS::FireOnNewGlobalHook, options));
+    if (!global2) {
+        fprintf(stderr, "failed to create second global");
+        return false;
+    }
+
+    // This should always be false, regardless.
+    if (global1->compartment() == global2->compartment()) {
+        fprintf(stderr, "second global claims to be in global1's compartment");
+        return false;
+    }
+
+    // This checks that the API obeys the implicit zone request.
+    if (global1->zone() == global2->zone()) {
+        fprintf(stderr, "global2 is in global1's zone");
+        return false;
+    }
+
+    // Define an object in compartment 2, that is wrapped by a CCW into compartment 1.
+    {
+        JSAutoCompartment ac(cx, global2);
+        wrappee.set(JS_NewPlainObject(cx));
+        if (wrappee->compartment() != global2->compartment()) {
+            fprintf(stderr, "wrappee in wrong compartment");
+            return false;
+        }
+    }
+
+    wrapper.set(wrappee);
+    if (!JS_WrapObject(cx, wrapper)) {
+        fprintf(stderr, "failed to wrap");
+        return false;
+    }
+    if (wrappee == wrapper) {
+        fprintf(stderr, "expected wrapping");
+        return false;
+    }
+    if (wrapper->compartment() != global1->compartment()) {
+        fprintf(stderr, "wrapper in wrong compartment");
+        return false;
+    }
+
+    return true;
+}
+
 class CCWTestTracer : public JS::CallbackTracer {
     void onChild(const JS::GCCellPtr& thing) override {
         numberOfThingsTraced++;
 
         printf("*thingp         = %p\n", thing.asCell());
         printf("*expectedThingp = %p\n", *expectedThingp);
 
         printf("kind         = %d\n", static_cast<int>(thing.kind()));
@@ -37,54 +96,191 @@ class CCWTestTracer : public JS::Callbac
         numberOfThingsTraced(0),
         expectedThingp(expectedThingp),
         expectedKind(expectedKind)
     { }
 };
 
 BEGIN_TEST(testTracingIncomingCCWs)
 {
-    // Get two globals, in two different zones.
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
 
     JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
-    CHECK(global1);
-    JS::CompartmentOptions options;
-    JS::RootedObject global2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
-                                                    JS::FireOnNewGlobalHook, options));
-    CHECK(global2);
-    CHECK(global1->compartment() != global2->compartment());
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    JS_GC(cx);
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+    CHECK(!js::gc::IsInsideNursery(wrapper));
 
-    // Define an object in one compartment, that is wrapped by a CCW in another
-    // compartment.
-
-    JS::RootedObject obj(cx, JS_NewPlainObject(cx));
-    CHECK(obj->compartment() == global1->compartment());
-
-    JSAutoCompartment ac(cx, global2);
-    JS::RootedObject wrapper(cx, obj);
-    CHECK(JS_WrapObject(cx, &wrapper));
     JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
-    CHECK(JS_SetProperty(cx, global2, "ccw", v));
+    CHECK(JS_SetProperty(cx, global1, "ccw", v));
 
     // Ensure that |TraceIncomingCCWs| finds the object wrapped by the CCW.
 
     JS::CompartmentSet compartments;
     CHECK(compartments.init());
-    CHECK(compartments.put(global1->compartment()));
+    CHECK(compartments.put(global2->compartment()));
 
-    void* thing = obj.get();
+    void* thing = wrappee.get();
     CCWTestTracer trc(cx, &thing, JS::TraceKind::Object);
     JS::TraceIncomingCCWs(&trc, compartments);
     CHECK(trc.numberOfThingsTraced == 1);
     CHECK(trc.okay);
 
     return true;
 }
 END_TEST(testTracingIncomingCCWs)
 
+static size_t
+countWrappers(JSCompartment* comp)
+{
+    size_t count = 0;
+    for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront())
+        ++count;
+    return count;
+}
+
+BEGIN_TEST(testDeadNurseryCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Now let the obj and wrapper die.
+    wrappee = wrapper = nullptr;
+
+    // Now a GC should clear the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 0);
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testDeadNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 1);
+
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+    CHECK(!js::gc::IsInsideNursery(wrapper));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryWrapperCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // The wrapper contains a strong reference to the wrappee, so just dropping
+    // the reference to the wrappee will not drop the CCW table entry as long
+    // as the wrapper is held strongly. Thus, the minor collection here must
+    // tenure both the wrapper and the wrappee and keep both in the table.
+    wrappee = nullptr;
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 1);
+
+    CHECK(!js::gc::IsInsideNursery(wrapper));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryWrapperCCW)
+
+BEGIN_TEST(testLiveNurseryWrappeeCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Let the wrapper die. The wrapper should drop from the table when we GC,
+    // even though there are other non-cross-compartment edges to it.
+    wrapper = nullptr;
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 0);
+
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryWrappeeCCW)
+
 BEGIN_TEST(testIncrementalRoots)
 {
     JSRuntime* rt = cx->runtime();
 
 #ifdef JS_GC_ZEAL
     // Disable zeal modes because this test needs to control exactly when the GC happens.
     JS_SetGCZeal(cx, 0, 100);
 #endif
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -186,16 +186,21 @@ JSObject* newCCW(JS::HandleObject source
         if (!object)
             return nullptr;
     }
     {
         JSAutoCompartment ac(cx, sourceZone);
         if (!JS_WrapObject(cx, &object))
             return nullptr;
     }
+
+    // In order to test the SCC algorithm, we need the wrapper/wrappee to be
+    // tenured.
+    cx->gc.evictNursery();
+
     return object;
 }
 
 JSObject* newDelegate()
 {
     static const js::ClassOps delegateClassOps = {
         nullptr, /* addProperty */
         nullptr, /* delProperty */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -228,44 +228,24 @@ JSCompartment::checkWrapperMapAfterMovin
         e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor());
 
         WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 #endif
 
-namespace {
-struct IsInsideNurseryFunctor {
-    template <class T> bool operator()(T tp) { return IsInsideNursery(*tp); }
-};
-} // namespace (anonymous)
-
 bool
 JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
                           const js::Value& wrapper)
 {
     MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
     MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
 
-    /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */
-    MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing())));
-
-    bool isNuseryKey =
-        const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) ||
-        const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor());
-
-    if (isNuseryKey && !nurseryCCKeys.append(wrapped)) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-
-    if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
-        if (isNuseryKey)
-            nurseryCCKeys.popBack();
+    if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
 static JSString*
@@ -601,26 +581,16 @@ JSCompartment::trace(JSTracer* trc)
 {
     savedStacks_.trace(trc);
 
     // Atoms are always tenured.
     if (!trc->runtime()->isHeapMinorCollecting())
         varNames_.trace(trc);
 }
 
-struct TraceFunctor {
-    JSTracer* trc_;
-    const char* name_;
-    TraceFunctor(JSTracer *trc, const char* name)
-      : trc_(trc), name_(name) {}
-    template <class T> void operator()(T* t) {
-        TraceManuallyBarrieredEdge(trc_, t, name_);
-    }
-};
-
 void
 JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark)
 {
     if (objectMetadataState.is<PendingMetadata>()) {
         TraceRoot(trc,
                   &objectMetadataState.as<PendingMetadata>(),
                   "on-stack object pending metadata");
     }
@@ -682,28 +652,16 @@ JSCompartment::traceRoots(JSTracer* trc,
             TraceRoot(trc, &script, "profilingScripts");
             MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
         }
     }
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->trace(trc);
 
-    // In a minor GC we need to mark nursery objects that are the targets of
-    // cross compartment wrappers.
-    if (trc->runtime()->isHeapMinorCollecting()) {
-        for (auto key : nurseryCCKeys) {
-            CrossCompartmentKey prior = key;
-            key.applyToWrapped(TraceFunctor(trc, "ccw wrapped"));
-            key.applyToDebugger(TraceFunctor(trc, "ccw debugger"));
-            crossCompartmentWrappers.rekeyIfMoved(prior, key);
-        }
-        nurseryCCKeys.clear();
-    }
-
     wasm.trace(trc);
 }
 
 void
 JSCompartment::finishRoots()
 {
     if (watchpointMap)
         watchpointMap->clear();
@@ -719,22 +677,24 @@ JSCompartment::finishRoots()
 
     clearScriptCounts();
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->clear();
 }
 
 void
-JSCompartment::sweepAfterMinorGC()
+JSCompartment::sweepAfterMinorGC(JSTracer* trc)
 {
     globalWriteBarriered = 0;
 
     if (innerViews.needsSweepAfterMinorGC())
         innerViews.sweepAfterMinorGC();
+
+    crossCompartmentWrappers.sweepAfterMinorGC(trc);
 }
 
 void
 JSCompartment::sweepSavedStacks()
 {
     savedStacks_.sweep();
 }
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -12,16 +12,17 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/Variant.h"
 #include "mozilla/XorShift128PlusRNG.h"
 
 #include "asmjs/WasmCompartment.h"
 #include "builtin/RegExp.h"
 #include "gc/Barrier.h"
+#include "gc/NurseryAwareHashMap.h"
 #include "gc/Zone.h"
 #include "vm/GlobalObject.h"
 #include "vm/PIC.h"
 #include "vm/SavedStacks.h"
 #include "vm/Time.h"
 
 namespace js {
 
@@ -63,17 +64,17 @@ class DtoaCache {
         this->s = s;
     }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); }
 #endif
 };
 
-struct CrossCompartmentKey
+class CrossCompartmentKey
 {
   public:
     enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject,
                                         DebuggerWasmScript, DebuggerWasmSource };
     using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
     using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
     using WrappedType = mozilla::Variant<
         JSObject*,
@@ -164,26 +165,38 @@ struct CrossCompartmentKey
         static HashNumber hash(const CrossCompartmentKey& key) {
             return key.wrapped.match(HashFunctor());
         }
 
         static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) {
             return l.wrapped == k.wrapped;
         }
     };
+
+    bool isTenured() const {
+        struct IsTenuredFunctor {
+            using ReturnType = bool;
+            ReturnType operator()(JSObject** tp) { return !IsInsideNursery(*tp); }
+            ReturnType operator()(JSScript** tp) { return true; }
+            ReturnType operator()(JSString** tp) { return true; }
+        };
+        return const_cast<CrossCompartmentKey*>(this)->applyToWrapped(IsTenuredFunctor());
+    }
+
     void trace(JSTracer* trc);
     bool needsSweep();
 
   private:
     CrossCompartmentKey() = delete;
     WrappedType wrapped;
 };
 
-using WrapperMap = GCRekeyableHashMap<CrossCompartmentKey, ReadBarrieredValue,
-                                      CrossCompartmentKey::Hasher, SystemAllocPolicy>;
+
+using WrapperMap = NurseryAwareHashMap<CrossCompartmentKey, JS::Value,
+                                       CrossCompartmentKey::Hasher, SystemAllocPolicy>;
 
 // We must ensure that all newly allocated JSObjects get their metadata
 // set. However, metadata builders may require the new object be in a sane
 // state (eg, have its reserved slots initialized so they can get the
 // sizeOfExcludingThis of the object). Therefore, for objects of certain
 // JSClasses (those marked with JSCLASS_DELAY_METADATA_BUILDER), it is not safe
 // for the allocation paths to call the object metadata builder
 // immediately. Instead, the JSClass-specific "constructor" C++ function up the
@@ -612,17 +625,17 @@ struct JSCompartment
      * when compacting to update cross-compartment pointers.
      */
     void traceOutgoingCrossCompartmentWrappers(JSTracer* trc);
     static void traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc);
 
     /* Whether to preserve JIT code on non-shrinking GCs. */
     bool preserveJitCode() { return creationOptions_.preserveJitCode(); }
 
-    void sweepAfterMinorGC();
+    void sweepAfterMinorGC(JSTracer* trc);
 
     void sweepCrossCompartmentWrappers();
     void sweepSavedStacks();
     void sweepGlobalObject(js::FreeOp* fop);
     void sweepSelfHostingScriptSource();
     void sweepJitCompartment(js::FreeOp* fop);
     void sweepRegExps();
     void sweepDebugEnvironments();
@@ -1061,9 +1074,16 @@ class MOZ_RAII AutoSuppressAllocationMet
 
     ~AutoSuppressAllocationMetadataBuilder() {
         zone->suppressAllocationMetadataBuilder = saved;
     }
 };
 
 } /* namespace js */
 
+namespace JS {
+template <>
+struct GCPolicy<js::CrossCompartmentKey> : public StructGCPolicy<js::CrossCompartmentKey> {
+    static bool isTenured(const js::CrossCompartmentKey& key) { return key.isTenured(); }
+};
+} // namespace JS
+
 #endif /* jscompartment_h */
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -105,17 +105,17 @@ JSCompartment::wrap(JSContext* cx, JS::M
 #ifdef DEBUG
     JS::RootedObject cacheResult(cx);
 #endif
     JS::RootedValue v(cx, vp);
     if (js::WrapperMap::Ptr p = crossCompartmentWrappers.lookup(js::CrossCompartmentKey(v))) {
 #ifdef DEBUG
         cacheResult = &p->value().get().toObject();
 #else
-        vp.set(p->value());
+        vp.set(p->value().get());
         return true;
 #endif
     }
 
     JS::RootedObject obj(cx, &vp.toObject());
     if (!wrap(cx, &obj))
         return false;
     vp.setObject(*obj);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3670,17 +3670,17 @@ InCrossCompartmentMap(JSObject* src, JS:
     }
 
     /*
      * If the cross-compartment edge is caused by the debugger, then we don't
      * know the right hashtable key, so we have to iterate.
      */
     for (JSCompartment::WrapperEnum e(srccomp); !e.empty(); e.popFront()) {
         if (e.front().mutableKey().applyToWrapped(IsDestComparatorFunctor(dst)) &&
-            ToMarkable(e.front().value()) == src)
+            ToMarkable(e.front().value().unbarrieredGet()) == src)
         {
             return true;
         }
     }
 
     return false;
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3686,18 +3686,19 @@ JSObject::allocKindForTenure(const js::N
      */
     if (is<TypedArrayObject>() && !as<TypedArrayObject>().hasBuffer()) {
         size_t nbytes = as<TypedArrayObject>().byteLength();
         if (nbytes >= TypedArrayObject::INLINE_BUFFER_LIMIT)
             return GetGCObjectKind(getClass());
         return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
     }
 
-    // Proxies have finalizers and are not nursery allocated.
-    MOZ_ASSERT(!IsProxy(this));
+    // Proxies that are CrossCompartmentWrappers may be nursery allocated.
+    if (IsProxy(this))
+        return as<ProxyObject>().allocKindForTenure();
 
     // Unboxed plain objects are sized according to the data they store.
     if (is<UnboxedPlainObject>()) {
         size_t nbytes = as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
         return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
     }
 
     // Unboxed arrays use inline data if their size is small enough.
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1124,19 +1124,25 @@ extern const char js_lookupGetter_str[];
 extern const char js_lookupSetter_str[];
 #endif
 
 namespace js {
 
 inline gc::InitialHeap
 GetInitialHeap(NewObjectKind newKind, const Class* clasp)
 {
+    if (newKind == NurseryAllocatedProxy) {
+        MOZ_ASSERT(clasp->isProxy());
+        MOZ_ASSERT(clasp->hasFinalize());
+        MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp));
+        return gc::DefaultHeap;
+    }
     if (newKind != GenericObject)
         return gc::TenuredHeap;
-    if (clasp->hasFinalize() && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
+    if (clasp->hasFinalize() && !CanNurseryAllocateFinalizedClass(clasp))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 bool
 NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
                                    NewObjectKind newKind, const Class* clasp);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -345,17 +345,18 @@ JSObject::create(js::ExclusiveContext* c
         MOZ_ASSERT(finalizeFlags == JSCLASS_FOREGROUND_FINALIZE ||
                    finalizeFlags == JSCLASS_BACKGROUND_FINALIZE);
         MOZ_ASSERT((finalizeFlags == JSCLASS_BACKGROUND_FINALIZE) == IsBackgroundFinalized(kind));
     } else {
         MOZ_ASSERT(finalizeFlags == 0);
     }
 
     MOZ_ASSERT_IF(clasp->hasFinalize(), heap == js::gc::TenuredHeap ||
-                                        (flags & JSCLASS_SKIP_NURSERY_FINALIZE));
+                                        CanNurseryAllocateFinalizedClass(clasp) ||
+                                        clasp->isProxy());
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(), heap == js::gc::TenuredHeap);
 #endif
 
     MOZ_ASSERT(!cx->compartment()->hasObjectPendingMetadata());
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -210,16 +210,19 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v,
                              bool* bp) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
 
+    // Allocate CrossCompartmentWrappers in the nursery.
+    virtual bool canNurseryAllocate() const override { return true; }
+
     static const CrossCompartmentWrapper singleton;
     static const CrossCompartmentWrapper singletonWithPrototype;
 };
 
 class JS_FRIEND_API(OpaqueCrossCompartmentWrapper) : public CrossCompartmentWrapper
 {
   public:
     explicit constexpr OpaqueCrossCompartmentWrapper() : CrossCompartmentWrapper(0)
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -135,19 +135,16 @@ else
     fi
     if test -z "$HOST_AR_FLAGS"; then
         HOST_AR_FLAGS='$(AR_FLAGS)'
     fi
 fi
 
 MOZ_TOOL_VARIABLES
 
-AC_PROG_CPP
-AC_PROG_CXXCPP
-
 dnl Special win32 checks
 dnl ========================================================
 
 # Target the Windows 8.1 SDK by default
 WINVER=502
 
 case "$target" in
 *-mingw*)
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -468,16 +468,17 @@ js::IsCrossCompartmentWrapper(JSObject* 
 {
     return IsWrapper(obj) &&
            !!(Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT);
 }
 
 void
 js::NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
 {
+    MOZ_ASSERT(!IsInsideNursery(wrapper));
     MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
 
     NotifyGCNukeWrapper(wrapper);
 
     wrapper->as<ProxyObject>().nuke(&DeadObjectProxy::singleton);
 
     MOZ_ASSERT(IsDeadProxyObject(wrapper));
 }
@@ -494,16 +495,18 @@ JS_FRIEND_API(bool)
 js::NukeCrossCompartmentWrappers(JSContext* cx,
                                  const CompartmentFilter& sourceFilter,
                                  const CompartmentFilter& targetFilter,
                                  js::NukeReferencesToWindow nukeReferencesToWindow)
 {
     CHECK_REQUEST(cx);
     JSRuntime* rt = cx->runtime();
 
+    rt->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
     // Iterate through scopes looking for system cross compartment wrappers
     // that point to an object that shares a global with obj.
 
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate the wrappers looking for anything interesting.
@@ -537,16 +540,19 @@ js::NukeCrossCompartmentWrappers(JSConte
 // Given a cross-compartment wrapper |wobj|, update it to point to
 // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
 // useful even if wrapper already points to newTarget.
 // This operation crashes on failure rather than leaving the heap in an
 // inconsistent state.
 void
 js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg)
 {
+    MOZ_ASSERT(!IsInsideNursery(wobjArg));
+    MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
     RootedObject wobj(cx, wobjArg);
     RootedObject newTarget(cx, newTargetArg);
     MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
     MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
     JSObject* origTarget = Wrapper::wrappedObject(wobj);
     MOZ_ASSERT(origTarget);
     Value origv = ObjectValue(*origTarget);
     JSCompartment* wcompartment = wobj->compartment();
@@ -601,16 +607,19 @@ js::RemapWrapper(JSContext* cx, JSObject
 }
 
 // Remap all cross-compartment wrappers pointing to |oldTarget| to point to
 // |newTarget|. All wrappers are recomputed.
 JS_FRIEND_API(bool)
 js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
                               JSObject* newTargetArg)
 {
+    MOZ_ASSERT(!IsInsideNursery(oldTargetArg));
+    MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
     RootedValue origv(cx, ObjectValue(*oldTargetArg));
     RootedObject newTarget(cx, newTargetArg);
 
     AutoWrapperVector toTransplant(cx);
     if (!toTransplant.reserve(cx->runtime()->numCompartments))
         return false;
 
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
@@ -625,18 +634,20 @@ js::RemapAllWrappersForObject(JSContext*
 
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
                       const CompartmentFilter& targetFilter)
 {
+    // Drop any nursery-allocated wrappers.
+    cx->runtime()->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
     AutoWrapperVector toRecompute(cx);
-
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate over the wrappers, filtering appropriately.
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             // Filter out non-objects.
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -772,16 +772,17 @@ js::NewProxyObject(JSContext* cx, const 
     }
 
     return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options);
 }
 
 void
 ProxyObject::renew(JSContext* cx, const BaseProxyHandler* handler, Value priv)
 {
+    MOZ_ASSERT(!IsInsideNursery(this));
     MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
     MOZ_ASSERT(getClass() == &ProxyObject::proxyClass);
     MOZ_ASSERT(!IsWindowProxy(this));
     MOZ_ASSERT(hasDynamicPrototype());
 
     setHandler(handler);
     setCrossCompartmentPrivate(priv);
     setExtra(0, UndefinedValue());
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/rest-parameter-names.js
@@ -0,0 +1,68 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288460;
+var summary =
+  "Rest parameters to functions can be named |yield| or |eval| or |let| in "
+  "non-strict code";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var f1 = (...yield) => yield + 42;
+assertEq(f1(), "42");
+assertEq(f1(1), "142");
+
+var f2 = (...eval) => eval + 42;
+assertEq(f2(), "42");
+assertEq(f2(1), "142");
+
+var f3 = (...let) => let + 42;
+assertEq(f3(), "42");
+assertEq(f3(1), "142");
+
+function g1(x, ...yield)
+{
+  return yield + x;
+}
+assertEq(g1(0, 42), "420");
+
+function g2(x, ...eval)
+{
+  return eval + x;
+}
+assertEq(g2(0, 42), "420");
+
+function g3(x, ...let)
+{
+  return let + x;
+}
+assertEq(g3(0, 42), "420");
+
+function h()
+{
+  "use strict";
+
+  var badNames = ["yield", "eval", "let"];
+
+  for (var badName of ["yield", "eval", "let"])
+  {
+    assertThrowsInstanceOf(() => eval(`var q = (...${badName}) => ${badName} + 42;`),
+                           SyntaxError);
+
+    assertThrowsInstanceOf(() => eval(`function r(x, ...${badName}) { return x + ${badName}; }`),
+                           SyntaxError);
+  }
+}
+h();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/compile-lastIndex.js
@@ -0,0 +1,88 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var BUGNUMBER = 1253099;
+var summary =
+  "RegExp.prototype.compile must perform all its steps *except* setting " +
+  ".lastIndex, then throw, when provided a RegExp whose .lastIndex has been " +
+  "made non-writable";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var regex = /foo/i;
+
+// Aside from making .lastIndex non-writable, this has two incidental effects
+// ubiquitously tested through the remainder of this test:
+//
+//   * RegExp.prototype.compile will do everything it ordinarily does, BUT it
+//     will throw a TypeError when attempting to zero .lastIndex immediately
+//     before succeeding overall.
+//   * RegExp.prototype.test for a non-global and non-sticky regular expression,
+//     in case of a match, will return true (as normal).  BUT if no match is
+//     found, it will throw a TypeError when attempting to modify .lastIndex.
+//
+// Ain't it great?
+Object.defineProperty(regex, "lastIndex", { value: 42, writable: false });
+
+assertEq(regex.global, false);
+assertEq(regex.ignoreCase, true);
+assertEq(regex.multiline, false);
+assertEq(regex.unicode, false);
+assertEq(regex.sticky, false);
+assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false);
+assertEq(regex.lastIndex, 42);
+
+assertEq(regex.test("foo"), true);
+assertEq(regex.test("FOO"), true);
+assertThrowsInstanceOf(() => regex.test("bar"), TypeError);
+assertThrowsInstanceOf(() => regex.test("BAR"), TypeError);
+
+assertThrowsInstanceOf(() => regex.compile("bar"), TypeError);
+
+assertEq(regex.global, false);
+assertEq(regex.ignoreCase, false);
+assertEq(regex.multiline, false);
+assertEq(regex.unicode, false);
+assertEq(regex.sticky, false);
+assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false);
+assertEq(regex.lastIndex, 42);
+assertThrowsInstanceOf(() => regex.test("foo"), TypeError);
+assertThrowsInstanceOf(() => regex.test("FOO"), TypeError);
+assertEq(regex.test("bar"), true);
+assertThrowsInstanceOf(() => regex.test("BAR"), TypeError);
+
+assertThrowsInstanceOf(() => regex.compile("^baz", "m"), TypeError);
+
+assertEq(regex.global, false);
+assertEq(regex.ignoreCase, false);
+assertEq(regex.multiline, true);
+assertEq(regex.unicode, false);
+assertEq(regex.sticky, false);
+assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false);
+assertEq(regex.lastIndex, 42);
+assertThrowsInstanceOf(() => regex.test("foo"), TypeError);
+assertThrowsInstanceOf(() => regex.test("FOO"), TypeError);
+assertThrowsInstanceOf(() => regex.test("bar"), TypeError);
+assertThrowsInstanceOf(() => regex.test("BAR"), TypeError);
+assertEq(regex.test("baz"), true);
+assertThrowsInstanceOf(() => regex.test("BAZ"), TypeError);
+assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901baz"),
+                       TypeError);
+assertEq(regex.test("012345678901234567890123456789012345678901\nbaz"), true);
+assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901BAZ"),
+                       TypeError);
+assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901\nBAZ"),
+                       TypeError);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Syntax/escaped-let-static-identifier.js
@@ -0,0 +1,57 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288460;
+var summary =
+  "|let| and |static| are forbidden as Identifier only in strict mode code, " +
+  "and it's permissible to use them as Identifier (with or without " +
+  "containing escapes) in non-strict code";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function t(code)
+{
+  var strictSemi = " 'use strict'; " + code;
+  var strictASI = " 'use strict' \n " + code;
+
+  var creationFunctions = [Function];
+  if (typeof evaluate === "function")
+    creationFunctions.push(evaluate);
+  if (typeof parseModule === "function")
+    creationFunctions.push(parseModule);
+
+  for (var func of creationFunctions)
+  {
+    if (typeof parseModule === "function" && func === parseModule)
+      assertThrowsInstanceOf(() => func(code), SyntaxError);
+    else
+      func(code);
+
+    assertThrowsInstanceOf(() => func(strictSemi), SyntaxError);
+    assertThrowsInstanceOf(() => func(strictASI), SyntaxError);
+  }
+}
+
+t("l\\u0065t: 42;");
+t("if (1) l\\u0065t: 42;");
+t("l\\u0065t = 42;");
+t("if (1) l\\u0065t = 42;");
+
+t("st\\u0061tic: 42;");
+t("if (1) st\\u0061tic: 42;");
+t("st\\u0061tic = 42;");
+t("if (1) st\\u0061tic = 42;");
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/tests/ecma_6/Syntax/let-as-label.js
+++ b/js/src/tests/ecma_6/Syntax/let-as-label.js
@@ -8,22 +8,28 @@ var BUGNUMBER = 1288459;
 var summary = "let can't be used as a label in strict mode code";
 
 print(BUGNUMBER + ": " + summary);
 
 /**************
  * BEGIN TEST *
  **************/
 
-Function("let: 42")
+Function("let: 42");
+Function("l\\u0065t: 42");
 assertThrowsInstanceOf(() => Function(" 'use strict'; let: 42"), SyntaxError);
 assertThrowsInstanceOf(() => Function(" 'use strict' \n let: 42"), SyntaxError);
+assertThrowsInstanceOf(() => Function(" 'use strict'; l\\u0065t: 42"), SyntaxError);
+assertThrowsInstanceOf(() => Function(" 'use strict' \n l\\u0065t: 42"), SyntaxError);
 
-eval("let: 42")
+eval("let: 42");
+eval("l\\u0065t: 42");
 assertThrowsInstanceOf(() => eval(" 'use strict'; let: 42"), SyntaxError);
 assertThrowsInstanceOf(() => eval(" 'use strict' \n let: 42;"), SyntaxError);
+assertThrowsInstanceOf(() => eval(" 'use strict'; l\\u0065t: 42"), SyntaxError);
+assertThrowsInstanceOf(() => eval(" 'use strict' \n l\\u0065t: 42;"), SyntaxError);
 
 /******************************************************************************/
 
 if (typeof reportCompare === "function")
   reportCompare(true, true);
 
 print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/extensions/reentrant-RegExp-creation-and-gc-during-new-RegExp-pattern-ToString.js
@@ -0,0 +1,41 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs gc (newGlobal/evaluate are shimmed)
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile =
+  "reentrant-RegExp-creation-and-gc-during-new-RegExp-pattern-ToString.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1253099;
+var summary =
+  "Don't assert when, in |new RegExp(pat)|, stringifying |pat| creates " +
+  "another RegExp and then performs a GC";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+// The fresh global object is required to ensure that the outer |new RegExp|
+// is the first RegExp created in the global (other than RegExp.prototype).
+newGlobal().evaluate(`
+var createsRegExpAndCallsGCWhenStringified =
+  {
+    toString: function() {
+      new RegExp("a");
+      gc();
+      return "q";
+    }
+  };
+
+assertEq(new RegExp(createsRegExpAndCallsGCWhenStringified).source, "q");
+`);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -619,22 +619,22 @@ WasmArrayRawBuffer::Allocate(uint32_t nu
 /* static */ void
 WasmArrayRawBuffer::Release(void* mem)
 {
     WasmArrayRawBuffer* header = (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));
     uint8_t* base = header->basePointer();
     MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
     size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
 
+    MemProfiler::RemoveNative(base);
 # ifdef XP_WIN
     VirtualFree(base, 0, MEM_RELEASE);
 # else  // XP_WIN
     munmap(base, mappedSizeWithHeader);
 # endif  // !XP_WIN
-    MemProfiler::RemoveNative(base);
 
 #  if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
     VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, mappedSizeWithHeader);
 #  endif
 }
 
 WasmArrayRawBuffer*
 ArrayBufferObject::BufferContents::wasmBuffer() const
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6180,17 +6180,17 @@ Debugger::observesFrame(AbstractFramePtr
 
 bool
 Debugger::observesFrame(const FrameIter& iter) const
 {
     // Skip frames not yet fully initialized during their prologue.
     if (iter.isInterp() && iter.isFunctionFrame()) {
         const Value& thisVal = iter.interpFrame()->thisArgument();
         if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING)
-            return false;;
+            return false;
     }
     if (iter.isWasm())
         return false;
     return observesScript(iter.script());
 }
 
 bool
 Debugger::observesScript(JSScript* script) const
@@ -9309,17 +9309,18 @@ DebuggerObject::initClass(JSContext* cx,
 
     return objectProto;
 }
 
 /* static */ DebuggerObject*
 DebuggerObject::create(JSContext* cx, HandleObject proto, HandleObject referent,
                        HandleNativeObject debugger)
 {
-  JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerObject::class_, proto, TenuredObject);
+  NewObjectKind newKind = IsInsideNursery(referent) ? GenericObject : TenuredObject;
+  JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerObject::class_, proto, newKind);
   if (!obj)
     return nullptr;
 
   DebuggerObject& object = obj->as<DebuggerObject>();
   object.setPrivateGCThing(referent);
   object.setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*debugger));
 
   return &object;
@@ -10448,18 +10449,18 @@ DebuggerEnvironment::initClass(JSContext
     return InitClass(cx, dbgCtor, objProto, &DebuggerEnvironment::class_, construct, 0,
                      properties_, methods_, nullptr, nullptr);
 }
 
 /* static */ DebuggerEnvironment*
 DebuggerEnvironment::create(JSContext* cx, HandleObject proto, HandleObject referent,
                             HandleNativeObject debugger)
 {
-    RootedObject obj(cx, NewObjectWithGivenProto(cx, &DebuggerEnvironment::class_, proto,
-                                                 TenuredObject));
+    NewObjectKind newKind = IsInsideNursery(referent) ? GenericObject : TenuredObject;
+    RootedObject obj(cx, NewObjectWithGivenProto(cx, &DebuggerEnvironment::class_, proto, newKind));
     if (!obj)
         return nullptr;
 
     DebuggerEnvironment& environment = obj->as<DebuggerEnvironment>();
     environment.setPrivateGCThing(referent);
     environment.setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
 
     return &environment;
--- a/js/src/vm/Keywords.h
+++ b/js/src/vm/Keywords.h
@@ -50,18 +50,16 @@
     macro(enum, enum_, TOK_RESERVED) \
     /* Future reserved keywords, but only in strict mode. */ \
     macro(implements, implements, TOK_STRICT_RESERVED) \
     macro(interface, interface, TOK_STRICT_RESERVED) \
     macro(package, package, TOK_STRICT_RESERVED) \
     macro(private, private_, TOK_STRICT_RESERVED) \
     macro(protected, protected_, TOK_STRICT_RESERVED) \
     macro(public, public_, TOK_STRICT_RESERVED) \
-    macro(static, static_, TOK_STRICT_RESERVED) \
     /* \
      * Yield is a token inside function*.  Outside of a function*, it is a \
      * future reserved keyword in strict mode, but a keyword in JS1.7 even \
      * when strict.  Punt logic to parser. \
      */ \
-    macro(yield, yield, TOK_YIELD) \
-    macro(let, let, TOK_LET)
+    macro(yield, yield, TOK_YIELD)
 
 #endif /* vm_Keywords_h */
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -42,16 +42,22 @@ enum NewObjectKind {
     /*
      * Singleton objects are treated specially by the type system. This flag
      * ensures that the new object is automatically set up correctly as a
      * singleton and is allocated in the tenured heap.
      */
     SingletonObject,
 
     /*
+     * CrossCompartmentWrappers use the common Proxy class, but are allowed
+     * to have nursery lifetime.
+     */
+    NurseryAllocatedProxy,
+
+    /*
      * Objects which will not benefit from being allocated in the nursery
      * (e.g. because they are known to have a long lifetime) may be allocated
      * with this kind to place them immediately into the tenured generation.
      */
     TenuredObject
 };
 
 /*
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -17,33 +17,45 @@ ProxyObject::New(JSContext* cx, const Ba
 {
     Rooted<TaggedProto> proto(cx, proto_);
 
     const Class* clasp = options.clasp();
 
     MOZ_ASSERT(isValidProxyClass(clasp));
     MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
     MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
+    MOZ_ASSERT(clasp->hasFinalize());
 
     /*
      * Eagerly mark properties unknown for proxies, so we don't try to track
      * their properties and so that we don't need to walk the compartment if
      * their prototype changes later.  But don't do this for DOM proxies,
      * because we want to be able to keep track of them in typesets in useful
      * ways.
      */
     if (proto.isObject() && !options.singleton() && !clasp->isDOMClass()) {
         RootedObject protoObj(cx, proto.toObject());
         if (!JSObject::setNewGroupUnknown(cx, clasp, protoObj))
             return nullptr;
     }
 
-    NewObjectKind newKind = options.singleton() ? SingletonObject : GenericObject;
+    // Ensure that the wrapper has the same lifetime assumptions as the
+    // wrappee. Prefer to allocate in the nursery, when possible.
+    NewObjectKind newKind = NurseryAllocatedProxy;
+    if (options.singleton()) {
+        MOZ_ASSERT(priv.isGCThing() && priv.toGCThing()->isTenured());
+        newKind = SingletonObject;
+    } else if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
+               !handler->canNurseryAllocate() ||
+               !handler->finalizeInBackground(priv))
+    {
+        newKind = TenuredObject;
+    }
+
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
-
     if (handler->finalizeInBackground(priv))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     AutoSetNewObjectMetadata metadata(cx);
     // Note: this will initialize the object's |data| to strange values, but we
     // will immediately overwrite those below.
     RootedObject obj(cx, NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind,
                                                        newKind));
@@ -57,16 +69,40 @@ ProxyObject::New(JSContext* cx, const Ba
 
     /* Don't track types of properties of non-DOM and non-singleton proxies. */
     if (newKind != SingletonObject && !clasp->isDOMClass())
         MarkObjectGroupUnknownProperties(cx, proxy->group());
 
     return proxy;
 }
 
+gc::AllocKind
+ProxyObject::allocKindForTenure() const
+{
+    gc::AllocKind allocKind = gc::GetGCObjectKind(group()->clasp());
+    if (data.handler->finalizeInBackground(const_cast<ProxyObject*>(this)->private_()))
+        allocKind = GetBackgroundAllocKind(allocKind);
+    return allocKind;
+}
+
+/* static */ size_t
+ProxyObject::objectMovedDuringMinorGC(TenuringTracer* trc, JSObject* dst, JSObject* src)
+{
+    ProxyObject& psrc = src->as<ProxyObject>();
+    ProxyObject& pdst = dst->as<ProxyObject>();
+
+    // We're about to sweep the nursery heap, so migrate the inline
+    // ProxyValueArray to the malloc heap if they were nursery allocated.
+    if (trc->runtime()->gc.nursery.isInside(psrc.data.values))
+        pdst.data.values = js_new<detail::ProxyValueArray>(*psrc.data.values);
+    else
+        trc->runtime()->gc.nursery.removeMallocedBuffer(psrc.data.values);
+    return sizeof(detail::ProxyValueArray);
+}
+
 void
 ProxyObject::setCrossCompartmentPrivate(const Value& priv)
 {
     *slotOfPrivate() = priv;
 }
 
 void
 ProxyObject::setSameCompartmentPrivate(const Value& priv)
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -72,16 +72,19 @@ class ProxyObject : public ShapedObject
     const Value& extra(size_t n) const {
         return GetProxyExtra(const_cast<ProxyObject*>(this), n);
     }
 
     void setExtra(size_t n, const Value& extra) {
         SetProxyExtra(this, n, extra);
     }
 
+    gc::AllocKind allocKindForTenure() const;
+    static size_t objectMovedDuringMinorGC(TenuringTracer* trc, JSObject* dst, JSObject* src);
+
   private:
     GCPtrValue* slotOfExtra(size_t n) {
         MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS);
         return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->values->extraSlots[n]);
     }
 
     static bool isValidProxyClass(const Class* clasp) {
         // Since we can take classes from the outside, make sure that they
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -480,16 +480,19 @@ class RegExpObject : public NativeObject
         MOZ_ASSERT(!maybeShared());
         NativeObject::setPrivate(&shared);
     }
 
     static void trace(JSTracer* trc, JSObject* obj);
 
     void initIgnoringLastIndex(HandleAtom source, RegExpFlag flags);
 
+    // NOTE: This method is *only* safe to call on RegExps that haven't been
+    //       exposed to script, because it requires that the "lastIndex"
+    //       property be writable.
     void initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, ExclusiveContext* cx);
 
   private:
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
     bool createShared(JSContext* cx, RegExpGuard* g);
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -842,16 +842,30 @@ TypeSet::Type::singletonNoBarrier() cons
 }
 
 inline ObjectGroup*
 TypeSet::Type::groupNoBarrier() const
 {
     return objectKey()->groupNoBarrier();
 }
 
+inline void
+TypeSet::Type::trace(JSTracer* trc)
+{
+    if (isSingletonUnchecked()) {
+        JSObject* obj = singletonNoBarrier();
+        TraceManuallyBarrieredEdge(trc, &obj, "TypeSet::Object");
+        *this = TypeSet::ObjectType(obj);
+    } else if (isGroupUnchecked()) {
+        ObjectGroup* group = groupNoBarrier();
+        TraceManuallyBarrieredEdge(trc, &group, "TypeSet::Group");
+        *this = TypeSet::ObjectType(group);
+    }
+}
+
 inline bool
 TypeSet::hasType(Type type) const
 {
     if (unknown())
         return true;
 
     if (type.isUnknown()) {
         return false;
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -345,19 +345,17 @@ class TypeSet
         }
         bool isGroupUnchecked() const {
             return isObjectUnchecked() && !(data & 1);
         }
 
         inline ObjectGroup* group() const;
         inline ObjectGroup* groupNoBarrier() const;
 
-        void trace(JSTracer* trc) {
-            MarkTypeUnbarriered(trc, this, "TypeSet::Type");
-        }
+        inline void trace(JSTracer* trc);
 
         bool operator == (Type o) const { return data == o.data; }
         bool operator != (Type o) const { return data != o.data; }
     } JS_HAZ_GC_POINTER;
 
     static inline Type UndefinedType() { return Type(JSVAL_TYPE_UNDEFINED); }
     static inline Type NullType()      { return Type(JSVAL_TYPE_NULL); }
     static inline Type BooleanType()   { return Type(JSVAL_TYPE_BOOLEAN); }
@@ -525,18 +523,16 @@ class TypeSet
 
     static inline bool IsUntrackedValue(const Value& val);
 
     // Get the type of a possibly optimized out or uninitialized let value.
     // This generally only happens on unconditional type monitors on bailing
     // out of Ion, such as for argument and local types.
     static inline Type GetMaybeUntrackedValueType(const Value& val);
 
-    static void MarkTypeRoot(JSTracer* trc, Type* v, const char* name);
-    static void MarkTypeUnbarriered(JSTracer* trc, Type* v, const char* name);
     static bool IsTypeMarked(Type* v);
     static bool IsTypeAllocatedDuringIncremental(Type v);
     static bool IsTypeAboutToBeFinalized(Type* v);
 } JS_HAZ_GC_POINTER;
 
 /*
  * A constraint which listens to additions to a type set and propagates those
  * changes to other type sets.
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -425,16 +425,19 @@ AddAnimationForProperty(nsIFrame* aFrame
   animation->duration() = computedTiming.mDuration;
   animation->iterations() = computedTiming.mIterations;
   animation->iterationStart() = computedTiming.mIterationStart;
   animation->direction() = static_cast<uint8_t>(timing.mDirection);
   animation->property() = aProperty.mProperty;
   animation->playbackRate() = aAnimation->PlaybackRate();
   animation->data() = aData;
   animation->easingFunction() = ToTimingFunction(timing.mFunction);
+  animation->iterationComposite() =
+    static_cast<uint8_t>(aAnimation->GetEffect()->
+                         AsKeyframeEffect()->IterationComposite());
 
   for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
     const AnimationPropertySegment& segment = aProperty.mSegments[segIdx];
 
     AnimationSegment* animSegment = animation->segments().AppendElement();
     if (aProperty.mProperty == eCSSProperty_transform) {
       animSegment->startState() = InfallibleTArray<TransformFunction>();
       animSegment->endState() = InfallibleTArray<TransformFunction>();
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1869,16 +1869,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
     }
   }
 
   for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
     shell->InvalidatePresShellIfHidden();
   }
   mPresShellsToInvalidateIfHidden.Clear();
 
+  bool notifyGC = false;
   if (mViewManagerFlushIsPending) {
     RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
 
     nsTArray<nsDocShell*> profilingDocShells;
     GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
     for (nsDocShell* docShell : profilingDocShells) {
       // For the sake of the profile timeline's simplicity, this is flagged as
       // paint even if it includes creating display lists
@@ -1904,20 +1905,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
 #endif
 
     for (nsDocShell* docShell : profilingDocShells) {
       MOZ_ASSERT(timelines);
       MOZ_ASSERT(timelines->HasConsumer(docShell));
       timelines->AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::END);
     }
 
-    if (nsContentUtils::XPConnect()) {
-      nsContentUtils::XPConnect()->NotifyDidPaint();
-      nsJSContext::NotifyDidPaint();
-    }
+    notifyGC = true;
   }
 
 #ifndef ANDROID  /* bug 1142079 */
   mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
 #endif
 
   nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
   while (iter.HasMore()) {
@@ -1927,16 +1925,21 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
 
   ConfigureHighPrecision();
 
   NS_ASSERTION(mInRefresh, "Still in refresh");
 
   if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
     ScheduleViewManagerFlush();
   }
+
+  if (notifyGC && nsContentUtils::XPConnect()) {
+    nsContentUtils::XPConnect()->NotifyDidPaint();
+    nsJSContext::NotifyDidPaint();
+  }
 }
 
 void
 nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
                                        mozilla::TimeStamp aDesired)
 {
   for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
     auto req = static_cast<imgIRequest*>(iter.Get()->GetKey());
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -1190,28 +1190,28 @@ struct nsGridContainerFrame::Tracks
                                  nscoord                     aPercentageBasis,
                                  IntrinsicISizeType          aConstraint,
                                  const LineRange&            aRange,
                                  const GridItemInfo&         aGridItem);
   /**
    * Collect the tracks which are growable (matching aSelector) into
    * aGrowableTracks, and return the amount of space that can be used
    * to grow those tracks.  Specifically, we return aAvailableSpace minus
-   * the sum of mBase's in aPlan (clamped to 0) for the tracks in aRange,
-   * or zero when there are no growable tracks.
+   * the sum of mBase's (and corresponding grid gaps) in aPlan (clamped to 0)
+   * for the tracks in aRange, or zero when there are no growable tracks.
    * @note aPlan[*].mBase represents a planned new base or limit.
    */
-  static nscoord CollectGrowable(nscoord                    aAvailableSpace,
-                                 const nsTArray<TrackSize>& aPlan,
-                                 const LineRange&           aRange,
-                                 TrackSize::StateBits       aSelector,
-                                 nsTArray<uint32_t>&        aGrowableTracks)
+  nscoord CollectGrowable(nscoord                    aAvailableSpace,
+                          const nsTArray<TrackSize>& aPlan,
+                          const LineRange&           aRange,
+                          TrackSize::StateBits       aSelector,
+                          nsTArray<uint32_t>&        aGrowableTracks) const
   {
     MOZ_ASSERT(aAvailableSpace > 0, "why call me?");
-    nscoord space = aAvailableSpace;
+    nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1);
     const uint32_t start = aRange.mStart;
     const uint32_t end = aRange.mEnd;
     for (uint32_t i = start; i < end; ++i) {
       const TrackSize& sz = aPlan[i];
       space -= sz.mBase;
       if (space <= 0) {
         return 0;
       }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-track-fit-content-sizing-002-ref.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: fit-content() track sizing</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1299133">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:16px; padding:0; margin:0; }
+
+.grid {
+  display: grid;
+  grid-auto-rows: 5px;
+  justify-content: start;
+  position: relative;
+  border: 1px solid;
+}
+
+.c1 { grid-template-columns: fit-content(40%) 25px minmax(100px, 1fr); }
+.c2 { grid-template-columns: fit-content(40%) 25px 0 25px minmax(100px, 1fr); }
+.c3 { grid-template-columns: fit-content(40%) 25px fit-content(40%) 25px minmax(0px, 1fr); }
+.c4 { grid-template-columns: fit-content(40%) 25px 100px 25px minmax(100px, 1fr); }
+.c5 { grid-template-columns: fit-content(40%) 25px minmax(0, 1fr) 25px minmax(100px, 1fr); }
+.c6 { grid-template-columns: fit-content(calc(1px - 99%)) 25px minmax(0, 1fr) 25px minmax(100px, 1fr); }
+.c7 { grid-template-columns: fit-content(40%) 25px fit-content(40%); }
+
+span {
+  grid-column: 1 / span 3;
+  grid-row: 2;
+  height: 2px;
+  background: lime;
+  min-width: 50px;
+}
+.c1 span { grid-column:1; }
+
+a {
+  display: inline-block;
+  width: 50px;
+  height: 1px;
+}
+
+y {
+    position: absolute;
+    left: 0; right: 0; top: 0;
+    height: 2px;
+    background: purple;
+    grid-column: 1 / 2;
+}
+y:nth-of-type(2n) { background: orange; grid-column: 3 / 4; }
+
+  </style>
+</head>
+<body>
+
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+
+<div style="width:502px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:442px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:382px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:322px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:262px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:202px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c7"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:142px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:82px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:22px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:2px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-track-fit-content-sizing-002.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: fit-content() track sizing</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1299133">
+  <link rel="help" href="https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content">
+  <link rel="match" href="grid-track-fit-content-sizing-002-ref.html">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:16px; padding:0; margin:0; }
+
+.grid {
+  display: grid;
+  grid-auto-rows: 5px;
+  grid-column-gap: 25px;
+  justify-content: start;
+  position: relative;
+  border: 1px solid;
+}
+
+.c1 { grid-template-columns: fit-content(40%) minmax(100px, 1fr); }
+.c2 { grid-template-columns: fit-content(40%) 0 minmax(100px, 1fr); }
+.c3 { grid-template-columns: fit-content(40%) fit-content(40%) minmax(0px, 1fr); }
+.c4 { grid-template-columns: fit-content(40%) 100px minmax(100px, 1fr); }
+.c5 { grid-template-columns: fit-content(40%) minmax(0, 1fr) minmax(100px, 1fr); }
+.c6 { grid-template-columns: fit-content(calc(1px - 99%)) minmax(0, 1fr) minmax(100px, 1fr); }
+.c7 { grid-template-columns: none; grid-auto-columns: fit-content(40%); }
+
+span {
+  grid-column: 1 / span 2;
+  grid-row: 2;
+  height: 2px;
+  background: lime;
+  min-width: 50px;
+}
+.c1 span { grid-column:1; }
+
+a {
+  display: inline-block;
+  width: 50px;
+  height: 1px;
+}
+
+y {
+    position: absolute;
+    left: 0; right: 0; top: 0;
+    height: 2px;
+    background: purple;
+    grid-column: 1 / 2;
+}
+y:nth-of-type(2n) { background: orange; grid-column: 2 / 3; }
+
+  </style>
+</head>
+<body>
+
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+
+<div style="width:502px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:442px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:382px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:322px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:262px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:202px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c7"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:142px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:82px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:22px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c6"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+<div style="width:2px">
+<div class="grid c1"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c2"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c3"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c4"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+<div class="grid c5"><span><a></a><a></a><a></a><a></a></span><y></y><y></y></div>
+</div>
+
+</body>
+</html>
--- a/layout/reftests/css-grid/reftest.list
+++ b/layout/reftests/css-grid/reftest.list
@@ -58,16 +58,17 @@ fuzzy-if(winWidget,1,36) == grid-auto-mi
 == grid-auto-min-sizing-intrinsic-004.html grid-auto-min-sizing-intrinsic-004-ref.html
 == grid-auto-min-sizing-percent-001.html grid-auto-min-sizing-percent-001-ref.html
 == grid-track-intrinsic-sizing-001.html grid-track-intrinsic-sizing-001-ref.html
 == grid-track-intrinsic-sizing-002.html grid-track-intrinsic-sizing-002-ref.html
 == grid-track-intrinsic-sizing-003.html grid-track-intrinsic-sizing-003-ref.html
 == grid-track-intrinsic-sizing-004.html grid-track-intrinsic-sizing-004-ref.html
 == grid-track-percent-sizing-001.html grid-track-percent-sizing-001-ref.html
 == grid-track-fit-content-sizing-001.html grid-track-fit-content-sizing-001-ref.html
+== grid-track-fit-content-sizing-002.html grid-track-fit-content-sizing-002-ref.html
 == grid-max-sizing-flex-001.html grid-max-sizing-flex-001-ref.html
 == grid-max-sizing-flex-002.html grid-max-sizing-flex-002-ref.html
 == grid-max-sizing-flex-003.html grid-max-sizing-flex-003-ref.html
 == grid-max-sizing-flex-004.html grid-max-sizing-flex-004-ref.html
 == grid-max-sizing-flex-005.html grid-max-sizing-flex-005-ref.html
 == grid-max-sizing-flex-006.html grid-max-sizing-flex-006-ref.html
 == grid-flex-min-sizing-001.html grid-flex-min-sizing-001-ref.html
 == grid-flex-min-sizing-002.html grid-flex-min-sizing-002-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/animation-utils.js
@@ -0,0 +1,13 @@
+function waitForIterationChange(animation) {
+  var initialIteration = animation.effect.getComputedTiming().currentIteration;
+  return new Promise(resolve => {
+    window.requestAnimationFrame(handleFrame = () => {
+      if (animation.effect.getComputedTiming().currentIteration !=
+            initialIteration) {
+        resolve();
+      } else {
+        window.requestAnimationFrame(handleFrame);
+      }
+    });
+  });
+}
--- a/layout/reftests/web-animations/reftest.list
+++ b/layout/reftests/web-animations/reftest.list
@@ -4,8 +4,11 @@ test-pref(dom.animations-api.core.enable
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-none-animation-before-appending-element.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-keyframe.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-target.html stacking-context-animation-changing-target-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-effect.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-keyframe.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-target.html stacking-context-animation-changing-target-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-effect.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-display-property.html stacking-context-animation-ref.html
+test-pref(dom.animations-api.core.enabled,true) == style-updates-on-iteration-composition-changed-from-accumulate-to-replace.html style-updates-for-iteration-composite-ref.html
+test-pref(dom.animations-api.core.enabled,true) == style-updates-on-iteration-composition-changed-from-replace-to-accumulate.html style-updates-for-iteration-composite-ref.html
+test-pref(dom.animations-api.core.enabled,true) == style-updates-on-current-iteration-changed.html style-updates-for-iteration-composite-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/style-updates-for-iteration-composite-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<title>Reference of testcases for bug 1216843</title>
+<style>
+#test {
+  width: 100px; height: 100px;
+  margin-left: 200px;
+  background: blue;
+}
+</style>
+<div id="test"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/style-updates-on-current-iteration-changed.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Update styles when current iteration changed</title>
+<script src="animation-utils.js"></script>
+<style>
+#test {
+  width: 100px; height: 100px;
+  background: blue;
+}
+</style>
+<div id="test"></div>
+<script>
+  var anim = document.getElementById("test")
+    .animate({ marginLeft: [ "0px", "100px" ] },
+             { duration: 100000,
+               delay: -99999, // For starting right before second iteration.
+               easing: "steps(1, start)",
+               iterations: 2,
+               iterationComposite: "accumulate" });
+
+  waitForIterationChange(anim).then(() => {
+    // Wait for painting the result of the second iteration.
+    requestAnimationFrame(() => {
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/style-updates-on-iteration-composition-changed-from-accumulate-to-replace.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Update styles when iteration composition changed from accumulate to
+replace</title>
+<script src="animation-utils.js"></script>
+<style>
+#test {
+  width: 100px; height: 100px;
+  background: blue;
+}
+</style>
+<div id="test"></div>
+<script>
+  var anim = document.getElementById("test")
+    .animate({ marginLeft: [ "0px", "200px" ] },
+             { duration: 100000,
+               delay: -99999, // For starting right before second iteration.
+               easing: "steps(1, start)",
+               iterations: 2,
+               iterationComposite: "accumulate" });
+
+  waitForIterationChange(anim).then(() => {
+    // Changing iterationComposite updates the element style.
+    anim.effect.iterationComposite = "replace";
+    requestAnimationFrame(() => {
+      // Now margin-left of the element should be 200px.
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/style-updates-on-iteration-composition-changed-from-replace-to-accumulate.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Update styles when iteration composition changed from replace to
+accumulate</title>
+<script src="animation-utils.js"></script>
+<style>
+#test {
+  width: 100px; height: 100px;
+  background: blue;
+}
+</style>
+<div id="test"></div>
+<script>
+  var anim = document.getElementById("test")
+    .animate({ marginLeft: [ "0px", "100px" ] },
+             { duration: 100000,
+               delay: -99999, // For starting right before second iteration.
+               easing: "steps(1, start)",
+               iterations: 2,
+               iterationComposite: "replace" });
+
+  waitForIterationChange(anim).then(() => {
+    // Changing iterationComposite updates the element style.
+    anim.effect.iterationComposite = "accumulate";
+    requestAnimationFrame(() => {
+      // Now margin-left of the element should be 200px.
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>
--- a/layout/style/GenerateCSSPropsGenerated.py
+++ b/layout/style/GenerateCSSPropsGenerated.py
@@ -5,17 +5,17 @@
 import sys
 import string
 import argparse
 import subprocess
 import buildconfig
 from mozbuild import shellutil
 
 def get_properties(preprocessorHeader):
-    cpp = shellutil.split(buildconfig.substs['CPP'])
+    cpp = list(buildconfig.substs['CPP'])
     cpp += shellutil.split(buildconfig.substs['ACDEFINES'])
     cpp.append(preprocessorHeader)
     preprocessed = subprocess.check_output(cpp)
     properties = [{"name":p[0], "prop":p[1], "id":p[2],
                    "flags":p[3], "pref":p[4], "proptype":p[5]}
                   for (i, p) in enumerate(eval(preprocessed))]
 
     # Sort the list so that longhand and logical properties are intermingled
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Utilities for animation of computed style values */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/StyleAnimationValue.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "nsStyleTransformMatrix.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsIStyleRule.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsString.h"
 #include "nsStyleContext.h"
@@ -561,18 +562,18 @@ StyleAnimationValue::ComputeDistance(nsC
       // that we should use Euclidean RGB cube distance.  However, we
       // have to extend that to RGBA.  For now, we'll just use the
       // Euclidean distance in the (part of the) 4-cube of premultiplied
       // colors.
       // FIXME (spec): The CSS transitions spec doesn't say whether
       // colors are premultiplied, but things work better when they are,
       // so use premultiplication.  Spec issue is still open per
       // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
-      nscolor startColor = aStartValue.GetColorValue();
-      nscolor endColor = aEndValue.GetColorValue();
+      nscolor startColor = aStartValue.GetCSSValueValue()->GetColorValue();
+      nscolor endColor = aEndValue.GetCSSValueValue()->GetColorValue();
 
       // Get a color component on a 0-1 scale, which is much easier to
       // deal with when working with alpha.
       #define GET_COMPONENT(component_, color_) \
         (NS_GET_##component_(color_) * (1.0 / 255.0))
 
       double startA = GET_COMPONENT(A, startColor);
       double startR = GET_COMPONENT(R, startColor) * startA;
@@ -1157,82 +1158,151 @@ AddCSSValuePercentNumber(const uint32_t 
   // aCoeff2 is 0, then we'll return the value halfway between 1 and
   // aValue1, rather than the value halfway between 0 and aValue1.
   // Note that we do something similar in AddTransformScale().
   float result = (n1 - aInitialVal) * aCoeff1 + (n2 - aInitialVal) * aCoeff2;
   aResult.SetFloatValue(RestrictValue(aValueRestrictions, result + aInitialVal),
                         eCSSUnit_Number);
 }
 
-static nscolor
-AddWeightedColors(double aCoeff1, nscolor aColor1,
-                  double aCoeff2, nscolor aColor2)
+// Returns Tuple(Red, Green, Blue, Alpha).
+// Red, Green, and Blue are scaled to the [0, 255] range, and Alpha is scaled
+// to the [0, 1] range (though values are allowed to fall outside of these
+// ranges).
+static Tuple<double, double, double, double>
+GetPremultipliedColorComponents(const nsCSSValue& aValue)
 {
+  // PercentageRGBColor and PercentageRGBAColor component value might be
+  // greater than 1.0 in case when the color value is accumulated, so we
+  // can't use nsCSSValue::GetColorValue() here because that function
+  // clamps its values.
+  if (aValue.GetUnit() == eCSSUnit_PercentageRGBColor ||
+      aValue.GetUnit() == eCSSUnit_PercentageRGBAColor) {
+    nsCSSValueFloatColor* floatColor = aValue.GetFloatColorValue();
+    double alpha = floatColor->Alpha();
+    return MakeTuple(floatColor->Comp1() * 255.0 * alpha,
+                     floatColor->Comp2() * 255.0 * alpha,
+                     floatColor->Comp3() * 255.0 * alpha,
+                     alpha);
+  }
+
+  nscolor color = aValue.GetColorValue();
+  double alpha = NS_GET_A(color) * (1.0 / 255.0);
+  return MakeTuple(NS_GET_R(color) * alpha,
+                   NS_GET_G(color) * alpha,
+                   NS_GET_B(color) * alpha,
+                   alpha);
+}
+
+enum class ColorAdditionType {
+  Clamped, // Clamp each color channel after adding.
+  Unclamped // Do not clamp color channels after adding.
+};
+
+// |aAdditionType| should be Clamped in case of interpolation or SMIL
+// animation (e.g. 'by' attribute). For now, Unclamped is only for
+// accumulation.
+static void
+AddWeightedColors(double aCoeff1, const nsCSSValue& aValue1,
+                  double aCoeff2, const nsCSSValue& aValue2,
+                  ColorAdditionType aAdditionType,
+                  nsCSSValue& aResult)
+{
+  MOZ_ASSERT(aValue1.IsNumericColorUnit() && aValue2.IsNumericColorUnit(),
+             "The unit should be color");
   // FIXME (spec): The CSS transitions spec doesn't say whether
   // colors are premultiplied, but things work better when they are,
   // so use premultiplication.  Spec issue is still open per
   // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
 
   // To save some math, scale the alpha down to a 0-1 scale, but
   // leave the color components on a 0-255 scale.
-  double A1 = NS_GET_A(aColor1) * (1.0 / 255.0);
-  double R1 = NS_GET_R(aColor1) * A1;
-  double G1 = NS_GET_G(aColor1) * A1;
-  double B1 = NS_GET_B(aColor1) * A1;
-  double A2 = NS_GET_A(aColor2) * (1.0 / 255.0);
-  double R2 = NS_GET_R(aColor2) * A2;
-  double G2 = NS_GET_G(aColor2) * A2;
-  double B2 = NS_GET_B(aColor2) * A2;
+
+  double R1, G1, B1, A1;
+  Tie(R1, G1, B1, A1) = GetPremultipliedColorComponents(aValue1);
+  double R2, G2, B2, A2;
+  Tie(R2, G2, B2, A2) = GetPremultipliedColorComponents(aValue2);
   double Aresf = (A1 * aCoeff1 + A2 * aCoeff2);
   if (Aresf <= 0.0) {
-    return NS_RGBA(0, 0, 0, 0);
+    aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
+    return;
   }
 
   if (Aresf > 1.0) {
     Aresf = 1.0;
   }
 
   double factor = 1.0 / Aresf;
-  uint8_t Ares = NSToIntRound(Aresf * 255.0);
-  uint8_t Rres = ClampColor((R1 * aCoeff1 + R2 * aCoeff2) * factor);
-  uint8_t Gres = ClampColor((G1 * aCoeff1 + G2 * aCoeff2) * factor);
-  uint8_t Bres = ClampColor((B1 * aCoeff1 + B2 * aCoeff2) * factor);
-  return NS_RGBA(Rres, Gres, Bres, Ares);
+  double Rres = (R1 * aCoeff1 + R2 * aCoeff2) * factor;
+  double Gres = (G1 * aCoeff1 + G2 * aCoeff2) * factor;
+  double Bres = (B1 * aCoeff1 + B2 * aCoeff2) * factor;
+
+  if (aAdditionType == ColorAdditionType::Clamped) {
+    aResult.SetColorValue(
+      NS_RGBA(ClampColor(Rres), ClampColor(Gres), ClampColor(Bres),
+              NSToIntRound(Aresf * 255.0)));
+    return;
+  }
+
+  Rres = Rres * (1.0 / 255.0);
+  Gres = Gres * (1.0 / 255.0);
+  Bres = Bres * (1.0 / 255.0);
+
+  aResult.SetFloatColorValue(Rres, Gres, Bres, Aresf,
+                             eCSSUnit_PercentageRGBAColor);
 }
 
-// Multiplies |aColor| by |aDilutionRatio| with premultiplication.
+// Multiplies |aValue| color by |aDilutionRation| with premultiplication.
+// The result is stored in |aResult|.
 // (The logic here should pretty closely match AddWeightedColors()' logic.)
-static nscolor
-DiluteColor(nscolor aColor, double aDilutionRatio)
+static void
+DiluteColor(const nsCSSValue& aValue, double aDilutionRatio,
+            nsCSSValue& aResult)
 {
+  MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color");
   MOZ_ASSERT(aDilutionRatio >= 0.0 && aDilutionRatio <= 1.0,
              "Dilution ratio should be in [0, 1]");
 
-  double A = NS_GET_A(aColor) * (1.0 / 255.0);
+  // Premultiplication
+  double R, G, B, A;
+  Tie(R, G, B, A) = GetPremultipliedColorComponents(aValue);
   double Aresf = A * aDilutionRatio;
   if (Aresf <= 0.0) {
-    return NS_RGBA(0, 0, 0, 0);
+    aResult.SetColorValue(NS_RGBA(0, 0, 0, 0));
+    return;
   }
 
-  // Premultiplication
-  double R = NS_GET_R(aColor) * A;
-  double G = NS_GET_G(aColor) * A;
-  double B = NS_GET_B(aColor) * A;
-
   double factor = 1.0 / Aresf;
-  return NS_RGBA(ClampColor(R * aDilutionRatio * factor),
-                 ClampColor(G * aDilutionRatio * factor),
-                 ClampColor(B * aDilutionRatio * factor),
-                 NSToIntRound(Aresf * 255.0));
+  aResult.SetColorValue(
+    NS_RGBA(ClampColor(R * aDilutionRatio * factor),
+            ClampColor(G * aDilutionRatio * factor),
+            ClampColor(B * aDilutionRatio * factor),
+            NSToIntRound(Aresf * 255.0)));
 }
 
-static bool
-AddShadowItems(double aCoeff1, const nsCSSValue &aValue1,
-               double aCoeff2, const nsCSSValue &aValue2,
-               nsCSSValueList **&aResultTail)
+void
+AppendToCSSValueList(UniquePtr<nsCSSValueList>& aHead,
+                     UniquePtr<nsCSSValueList>&& aValueToAppend,
+                     nsCSSValueList** aTail)
+{
+  MOZ_ASSERT(!aHead == !*aTail,
+             "Can't have head w/o tail, & vice versa");
+
+  if (!aHead) {
+    aHead = Move(aValueToAppend);
+    *aTail = aHead.get();
+  } else {
+    (*aTail) = (*aTail)->mNext = aValueToAppend.release();
+  }
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedShadowItems(double aCoeff1, const nsCSSValue &aValue1,
+                       double aCoeff2, const nsCSSValue &aValue2,
+                       ColorAdditionType aColorAdditionType)
 {
   // X, Y, Radius, Spread, Color, Inset
   MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Array,
              "wrong unit");
   MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Array,
              "wrong unit");
   nsCSSValue::Array *array1 = aValue1.GetArrayValue();
   nsCSSValue::Array *array2 = aValue2.GetArrayValue();
@@ -1244,46 +1314,42 @@ AddShadowItems(double aCoeff1, const nsC
                      // blur radius must be nonnegative
                      (i == 2) ? CSS_PROPERTY_VALUE_NONNEGATIVE : 0);
   }
 
   const nsCSSValue& color1 = array1->Item(4);
   const nsCSSValue& color2 = array2->Item(4);
   const nsCSSValue& inset1 = array1->Item(5);
   const nsCSSValue& inset2 = array2->Item(5);
-  if (color1.GetUnit() != color2.GetUnit() ||
+  if ((color1.GetUnit() != color2.GetUnit() &&
+       (!color1.IsNumericColorUnit() || !color2.IsNumericColorUnit())) ||
       inset1.GetUnit() != inset2.GetUnit()) {
     // We don't know how to animate between color and no-color, or
     // between inset and not-inset.
-    return false;
+    // NOTE: In case when both colors' units are eCSSUnit_Null, that means
+    // neither color value was specified, so we can interpolate.
+    return nullptr;
   }
 
   if (color1.GetUnit() != eCSSUnit_Null) {
-    StyleAnimationValue color1Value
-      (color1.GetColorValue(), StyleAnimationValue::ColorConstructor);
-    StyleAnimationValue color2Value
-      (color2.GetColorValue(), StyleAnimationValue::ColorConstructor);
-    StyleAnimationValue resultColorValue;
-    DebugOnly<bool> ok =
-      StyleAnimationValue::AddWeighted(eCSSProperty_color,
-                                       aCoeff1, color1Value,
-                                       aCoeff2, color2Value,
-                                       resultColorValue);
-    MOZ_ASSERT(ok, "should not fail");
-    resultArray->Item(4).SetColorValue(resultColorValue.GetColorValue());
+    if (aCoeff2 == 0.0 && aCoeff1 != 1.0) {
+      DiluteColor(color1, aCoeff1, resultArray->Item(4));
+    } else {
+      AddWeightedColors(aCoeff1, color1, aCoeff2, color2,
+                        aColorAdditionType,
+                        resultArray->Item(4));
+    }
   }
 
   MOZ_ASSERT(inset1 == inset2, "should match");
   resultArray->Item(5) = inset1;
 
-  nsCSSValueList *resultItem = new nsCSSValueList;
+  auto resultItem = MakeUnique<nsCSSValueList>();
   resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
-  *aResultTail = resultItem;
-  aResultTail = &resultItem->mNext;
-  return true;
+  return resultItem;
 }
 
 static void
 AddTransformTranslate(double aCoeff1, const nsCSSValue &aValue1,
                       double aCoeff2, const nsCSSValue &aValue2,
                       nsCSSValue &aResult)
 {
   MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Percent ||
@@ -1733,38 +1799,39 @@ AddDifferentTransformLists(double aCoeff
 }
 
 static bool
 TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
 {
   return ToPrimitive(func1) == ToPrimitive(func2);
 }
 
-static bool
-AddFilterFunctionImpl(double aCoeff1, const nsCSSValueList* aList1,
-                      double aCoeff2, const nsCSSValueList* aList2,
-                      nsCSSValueList**& aResultTail)
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterFunctionImpl(double aCoeff1, const nsCSSValueList* aList1,
+                              double aCoeff2, const nsCSSValueList* aList2,
+                              ColorAdditionType aColorAdditionType)
 {
-  // AddFilterFunction should be our only caller, and it should ensure that both
-  // args are non-null.
+  // AddWeightedFilterFunction should be our only caller, and it should ensure
+  // that both args are non-null.
   MOZ_ASSERT(aList1, "expected filter list");
   MOZ_ASSERT(aList2, "expected filter list");
   MOZ_ASSERT(aList1->mValue.GetUnit() == eCSSUnit_Function,
              "expected function");
   MOZ_ASSERT(aList2->mValue.GetUnit() == eCSSUnit_Function,
              "expected function");
   RefPtr<nsCSSValue::Array> a1 = aList1->mValue.GetArrayValue(),
                               a2 = aList2->mValue.GetArrayValue();
   nsCSSKeyword filterFunction = a1->Item(0).GetKeywordValue();
-  if (filterFunction != a2->Item(0).GetKeywordValue())
-    return false; // Can't add two filters of different types.
-
-  nsAutoPtr<nsCSSValueList> resultListEntry(new nsCSSValueList);
+  if (filterFunction != a2->Item(0).GetKeywordValue()) {
+    return nullptr; // Can't add two filters of different types.
+  }
+
+  auto resultList = MakeUnique<nsCSSValueList>();
   nsCSSValue::Array* result =
-    resultListEntry->mValue.InitFunction(filterFunction, 1);
+    resultList->mValue.InitFunction(filterFunction, 1);
 
   // "hue-rotate" is the only filter-function that accepts negative values, and
   // we don't use this "restrictions" variable in its clause below.
   const uint32_t restrictions = CSS_PROPERTY_VALUE_NONNEGATIVE;
   const nsCSSValue& funcArg1 = a1->Item(1);
   const nsCSSValue& funcArg2 = a2->Item(1);
   nsCSSValue& resultArg = result->Item(1);
   float initialVal = 1.0f;
@@ -1777,17 +1844,17 @@ AddFilterFunctionImpl(double aCoeff1, co
         // If units differ, we'll just combine them with calc().
         unit = eCSSUnit_Calc;
       }
       if (!AddCSSValuePixelPercentCalc(restrictions,
                                        unit,
                                        aCoeff1, funcArg1,
                                        aCoeff2, funcArg2,
                                        resultArg)) {
-        return false;
+        return nullptr;
       }
       break;
     }
     case eCSSKeyword_grayscale:
     case eCSSKeyword_invert:
     case eCSSKeyword_sepia:
       initialVal = 0.0f;
       MOZ_FALLTHROUGH;
@@ -1802,61 +1869,62 @@ AddFilterFunctionImpl(double aCoeff1, co
                                initialVal);
       break;
     case eCSSKeyword_hue_rotate:
       AddCSSValueAngle(aCoeff1, funcArg1,
                        aCoeff2, funcArg2,
                        resultArg);
       break;
     case eCSSKeyword_drop_shadow: {
-      nsCSSValueList* resultShadow = resultArg.SetListValue();
-      nsAutoPtr<nsCSSValueList> shadowValue;
-      nsCSSValueList **shadowTail = getter_Transfers(shadowValue);
       MOZ_ASSERT(!funcArg1.GetListValue()->mNext &&
                  !funcArg2.GetListValue()->mNext,
                  "drop-shadow filter func doesn't support lists");
-      if (!AddShadowItems(aCoeff1, funcArg1.GetListValue()->mValue,
-                          aCoeff2, funcArg2.GetListValue()->mValue,
-                          shadowTail)) {
-        return false;
+      UniquePtr<nsCSSValueList> shadowValue =
+        AddWeightedShadowItems(aCoeff1,
+                               funcArg1.GetListValue()->mValue,
+                               aCoeff2,
+                               funcArg2.GetListValue()->mValue,
+                               aColorAdditionType);
+      if (!shadowValue) {
+        return nullptr;
       }
-      *resultShadow = *shadowValue;
+      resultArg.AdoptListValue(Move(shadowValue));
       break;
     }
     default:
       MOZ_ASSERT(false, "unknown filter function");
-      return false;
+      return nullptr;
   }
 
-  *aResultTail = resultListEntry.forget();
-  aResultTail = &(*aResultTail)->mNext;
-
-  return true;
+  return resultList;
 }
 
-static bool
-AddFilterFunction(double aCoeff1, const nsCSSValueList* aList1,
-                  double aCoeff2, const nsCSSValueList* aList2,
-                  nsCSSValueList**& aResultTail)
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterFunction(double aCoeff1, const nsCSSValueList* aList1,
+                          double aCoeff2, const nsCSSValueList* aList2,
+                          ColorAdditionType aColorAdditionType)
 {
   MOZ_ASSERT(aList1 || aList2,
              "one function list item must not be null");
   // Note that one of our arguments could be null, indicating that
   // it's the initial value. Rather than adding special null-handling
   // logic, we just check for null values and replace them with
-  // 0 * the other value. That way, AddFilterFunctionImpl can assume
+  // 0 * the other value. That way, AddWeightedFilterFunctionImpl can assume
   // its args are non-null.
   if (!aList1) {
-    return AddFilterFunctionImpl(aCoeff2, aList2, 0, aList2, aResultTail);
+    return AddWeightedFilterFunctionImpl(aCoeff2, aList2, 0, aList2,
+                                         aColorAdditionType);
   }
   if (!aList2) {
-    return AddFilterFunctionImpl(aCoeff1, aList1, 0, aList1, aResultTail);
+    return AddWeightedFilterFunctionImpl(aCoeff1, aList1, 0, aList1,
+                                         aColorAdditionType);
   }
 
-  return AddFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2, aResultTail);
+  return AddWeightedFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2,
+                                       aColorAdditionType);
 }
 
 static inline uint32_t
 ShapeArgumentCount(nsCSSKeyword aShapeFunction)
 {
   switch (aShapeFunction) {
     case eCSSKeyword_circle:
       return 2; // radius and center point
@@ -2304,16 +2372,108 @@ AddPositionCoords(double aCoeff1, const 
    */
   const nsCSSValue& v1 = posArray1->Item(1);
   const nsCSSValue& v2 = posArray2->Item(1);
   nsCSSValue& vr = resultPosArray->Item(1);
   AddCSSValueCanonicalCalc(aCoeff1, v1,
                            aCoeff2, v2, vr);
 }
 
+static UniquePtr<nsCSSValueList>
+AddWeightedShadowList(double aCoeff1,
+                      const nsCSSValueList* aShadow1,
+                      double aCoeff2,
+                      const nsCSSValueList* aShadow2,
+                      ColorAdditionType aColorAdditionType)
+{
+  // This is implemented according to:
+  // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+  // and the third item in the summary of:
+  // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
+  UniquePtr<nsCSSValueList> result;
+  nsCSSValueList* tail = nullptr;
+  while (aShadow1 && aShadow2) {
+    UniquePtr<nsCSSValueList> shadowValue =
+      AddWeightedShadowItems(aCoeff1, aShadow1->mValue,
+                             aCoeff2, aShadow2->mValue,
+                             aColorAdditionType);
+    if (!shadowValue) {
+      return nullptr;
+    }
+    aShadow1 = aShadow1->mNext;
+    aShadow2 = aShadow2->mNext;
+    AppendToCSSValueList(result, Move(shadowValue), &tail);
+  }
+  if (aShadow1 || aShadow2) {
+    const nsCSSValueList *longShadow;
+    double longCoeff;
+    if (aShadow1) {
+      longShadow = aShadow1;
+      longCoeff = aCoeff1;
+    } else {
+      longShadow = aShadow2;
+      longCoeff = aCoeff2;
+    }
+
+    while (longShadow) {
+      // Passing coefficients that add to less than 1 produces the
+      // desired result of interpolating "0 0 0 transparent" with
+      // the current shadow.
+      UniquePtr<nsCSSValueList> shadowValue =
+        AddWeightedShadowItems(longCoeff, longShadow->mValue,
+                               0.0, longShadow->mValue,
+                               aColorAdditionType);
+      if (!shadowValue) {
+        return nullptr;
+      }
+
+      longShadow = longShadow->mNext;
+      AppendToCSSValueList(result, Move(shadowValue), &tail);
+    }
+  }
+  return result;
+}
+
+static UniquePtr<nsCSSValueList>
+AddWeightedFilterList(double aCoeff1, const nsCSSValueList* aList1,
+                      double aCoeff2, const nsCSSValueList* aList2,
+                      ColorAdditionType aColorAdditionType)
+{
+  UniquePtr<nsCSSValueList> result;
+  nsCSSValueList* tail = nullptr;
+  while (aList1 || aList2) {
+    if ((aList1 && aList1->mValue.GetUnit() != eCSSUnit_Function) ||
+        (aList2 && aList2->mValue.GetUnit() != eCSSUnit_Function)) {
+      // If we don't have filter-functions, we must have filter-URLs, which
+      // we can't add or interpolate.
+      return nullptr;
+    }
+
+    UniquePtr<nsCSSValueList> resultFunction =
+      AddWeightedFilterFunction(aCoeff1, aList1, aCoeff2, aList2,
+                                aColorAdditionType);
+    if (!resultFunction) {
+      // filter function mismatch
+      return nullptr;
+    }
+
+    AppendToCSSValueList(result, Move(resultFunction), &tail);
+
+    // move to next aList items
+    if (aList1) {
+      aList1 = aList1->mNext;
+    }
+    if (aList2) {
+      aList2 = aList2->mNext;
+    }
+  }
+
+  return result;
+}
+
 bool
 StyleAnimationValue::AddWeighted(nsCSSPropertyID aProperty,
                                  double aCoeff1,
                                  const StyleAnimationValue& aValue1,
                                  double aCoeff2,
                                  const StyleAnimationValue& aValue2,
                                  StyleAnimationValue& aResultValue)
 {
@@ -2403,31 +2563,34 @@ StyleAnimationValue::AddWeighted(nsCSSPr
     }
     case eUnit_Float: {
       aResultValue.SetFloatValue(RestrictValue(aProperty,
         aCoeff1 * aValue1.GetFloatValue() +
         aCoeff2 * aValue2.GetFloatValue()));
       return true;
     }
     case eUnit_Color: {
-      nscolor color1 = aValue1.GetColorValue();
-      nscolor color2 = aValue2.GetColorValue();
-      nscolor resultColor;
+      const nsCSSValue* value1 = aValue1.GetCSSValueValue();
+      const nsCSSValue* value2 = aValue2.GetCSSValueValue();
+      MOZ_ASSERT(value1 && value2, "Both of CSS value should be valid");
+      auto resultColor = MakeUnique<nsCSSValue>();
 
       // We are using AddWeighted() with a zero aCoeff2 for colors to
       // pretend AddWeighted() against transparent color, i.e. rgba(0, 0, 0, 0).
       // But unpremultiplication in AddWeightedColors() does not work well
       // for such cases, so we use another function named DiluteColor() which
       // has a similar logic to AddWeightedColors().
       if (aCoeff2 == 0.0) {
-        resultColor = DiluteColor(color1, aCoeff1);
+        DiluteColor(*value1, aCoeff1, *resultColor);
       } else {
-        resultColor = AddWeightedColors(aCoeff1, color1, aCoeff2, color2);
+        AddWeightedColors(aCoeff1, *value1, aCoeff2, *value2,
+                          ColorAdditionType::Clamped,
+                          *resultColor);
       }
-      aResultValue.SetColorValue(resultColor);
+      aResultValue.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
       return true;
     }
     case eUnit_Calc: {
       PixelCalcValue v1 = ExtractCalcValue(aValue1);
       PixelCalcValue v2 = ExtractCalcValue(aValue2);
       double len = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength;
       double pct = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent;
       bool hasPct = (aCoeff1 != 0.0 && v1.mHasPercent) ||
@@ -2599,104 +2762,49 @@ StyleAnimationValue::AddWeighted(nsCSSPr
         }
       }
 
       aResultValue.SetAndAdoptCSSValueListValue(result.forget(),
                                                 eUnit_Dasharray);
       return true;
     }
     case eUnit_Shadow: {
-      // This is implemented according to:
-      // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
-      // and the third item in the summary of:
-      // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
-      const nsCSSValueList *shadow1 = aValue1.GetCSSValueListValue();
-      const nsCSSValueList *shadow2 = aValue2.GetCSSValueListValue();
-      nsAutoPtr<nsCSSValueList> result;
-      nsCSSValueList **resultTail = getter_Transfers(result);
-      while (shadow1 && shadow2) {
-        if (!AddShadowItems(aCoeff1, shadow1->mValue,
-                            aCoeff2, shadow2->mValue,
-                            resultTail)) {
-          return false;
-        }
-        shadow1 = shadow1->mNext;
-        shadow2 = shadow2->mNext;
+      UniquePtr<nsCSSValueList> result =
+        AddWeightedShadowList(aCoeff1,
+                              aValue1.GetCSSValueListValue(),
+                              aCoeff2,
+                              aValue2.GetCSSValueListValue(),
+                              ColorAdditionType::Clamped);
+      if (!result) {
+        return false;
       }
-      if (shadow1 || shadow2) {
-        const nsCSSValueList *longShadow;
-        double longCoeff;
-        if (shadow1) {
-          longShadow = shadow1;
-          longCoeff = aCoeff1;
-        } else {
-          longShadow = shadow2;
-          longCoeff = aCoeff2;
-        }
-
-        while (longShadow) {
-          // Passing coefficients that add to less than 1 produces the
-          // desired result of interpolating "0 0 0 transparent" with
-          // the current shadow.
-          if (!AddShadowItems(longCoeff, longShadow->mValue,
-                              0.0, longShadow->mValue,
-                              resultTail)) {
-            return false;
-          }
-
-          longShadow = longShadow->mNext;
-        }
-      }
-      aResultValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow);
+      aResultValue.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow);
       return true;
     }
     case eUnit_Shape: {
       RefPtr<nsCSSValue::Array> result =
         AddShapeFunction(aProperty,
                          aCoeff1, aValue1.GetCSSValueArrayValue(),
                          aCoeff2, aValue2.GetCSSValueArrayValue());
       if (!result) {
         return false;
       }
       aResultValue.SetCSSValueArrayValue(result, eUnit_Shape);
       return true;
     }
     case eUnit_Filter: {
-      const nsCSSValueList *list1 = aValue1.GetCSSValueListValue();
-      const nsCSSValueList *list2 = aValue2.GetCSSValueListValue();
-
-      nsAutoPtr<nsCSSValueList> result;
-      nsCSSValueList **resultTail = getter_Transfers(result);
-      while (list1 || list2) {
-        MOZ_ASSERT(!*resultTail,
-          "resultTail isn't pointing to the tail (may leak)");
-        if ((list1 && list1->mValue.GetUnit() != eCSSUnit_Function) ||
-            (list2 && list2->mValue.GetUnit() != eCSSUnit_Function)) {
-          // If we don't have filter-functions, we must have filter-URLs, which
-          // we can't add or interpolate.
-          return false;
-        }
-
-        if (!AddFilterFunction(aCoeff1, list1, aCoeff2, list2, resultTail)) {
-          // filter function mismatch
-          return false;
-        }
-
-        // move to next list items
-        if (list1) {
-          list1 = list1->mNext;
-        }
-        if (list2) {
-          list2 = list2->mNext;
-        }
+      UniquePtr<nsCSSValueList> result =
+        AddWeightedFilterList(aCoeff1, aValue1.GetCSSValueListValue(),
+                              aCoeff2, aValue2.GetCSSValueListValue(),
+                              ColorAdditionType::Clamped);
+      if (!result) {
+        return false;
       }
-      MOZ_ASSERT(!*resultTail,
-                 "resultTail isn't pointing to the tail (may leak)");
-
-      aResultValue.SetAndAdoptCSSValueListValue(result.forget(),
+
+      aResultValue.SetAndAdoptCSSValueListValue(result.release(),
                                                 eUnit_Filter);
       return true;
     }
 
     case eUnit_Transform: {
       const nsCSSValueList* list1 = aValue1.GetCSSValueSharedListValue()->mHead;
       const nsCSSValueList* list2 = aValue2.GetCSSValueSharedListValue()->mHead;
 
@@ -2796,16 +2904,67 @@ StyleAnimationValue::AddWeighted(nsCSSPr
       return true;
     }
   }
 
   MOZ_ASSERT(false, "Can't interpolate using the given common unit");
   return false;
 }
 
+bool
+StyleAnimationValue::Accumulate(nsCSSPropertyID aProperty,
+                                StyleAnimationValue& aDest,
+                                const StyleAnimationValue& aValueToAccumulate,
+                                uint64_t aCount)
+{
+  Unit commonUnit =
+    GetCommonUnit(aProperty, aDest.GetUnit(), aValueToAccumulate.GetUnit());
+  switch (commonUnit) {
+    case eUnit_Filter: {
+      UniquePtr<nsCSSValueList> result =
+        AddWeightedFilterList(1.0, aDest.GetCSSValueListValue(),
+                              aCount, aValueToAccumulate.GetCSSValueListValue(),
+                              ColorAdditionType::Unclamped);
+      if (!result) {
+        return false;
+      }
+
+      aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Filter);
+      return true;
+    }
+    case eUnit_Shadow: {
+      UniquePtr<nsCSSValueList> result =
+        AddWeightedShadowList(1.0, aDest.GetCSSValueListValue(),
+                              aCount, aValueToAccumulate.GetCSSValueListValue(),
+                              ColorAdditionType::Unclamped);
+      if (!result) {
+        return false;
+      }
+      aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow);
+      return true;
+    }
+    case eUnit_Color: {
+      auto resultColor = MakeUnique<nsCSSValue>();
+      AddWeightedColors(1.0,
+                        *aDest.GetCSSValueValue(),
+                        aCount,
+                        *aValueToAccumulate.GetCSSValueValue(),
+                        ColorAdditionType::Unclamped,
+                        *resultColor);
+
+      aDest.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color);
+      return true;
+    }
+    default:
+      return Add(aProperty, aDest, aValueToAccumulate, aCount);
+  }
+  MOZ_ASSERT_UNREACHABLE("Can't accumulate using the given common unit");
+  return false;
+}
+
 already_AddRefed<css::StyleRule>
 BuildStyleRule(nsCSSPropertyID aProperty,
                dom::Element* aTargetElement,
                const nsAString& aSpecifiedValue,
                bool aUseSVGMode)
 {
   // Set up an empty CSS Declaration
   RefPtr<css::Declaration> declaration(new css::Declaration());
@@ -3093,31 +3252,30 @@ StyleAnimationValue::UncomputeValue(nsCS
       break;
     case eUnit_Percent:
       aSpecifiedValue.SetPercentValue(aComputedValue.GetPercentValue());
       break;
     case eUnit_Float:
       aSpecifiedValue.
         SetFloatValue(aComputedValue.GetFloatValue(), eCSSUnit_Number);
       break;
-    case eUnit_Color:
-      // colors can be alone, or part of a paint server
-      aSpecifiedValue.SetColorValue(aComputedValue.GetColorValue());
-      break;
     case eUnit_CurrentColor:
       aSpecifiedValue.SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
       break;
     case eUnit_Calc:
+    case eUnit_Color:
     case eUnit_ObjectPosition:
     case eUnit_URL:
     case eUnit_DiscreteCSSValue: {
       nsCSSValue* val = aComputedValue.GetCSSValueValue();
       // Sanity-check that the underlying unit in the nsCSSValue is what we
       // expect for our StyleAnimationValue::Unit:
       MOZ_ASSERT((unit == eUnit_Calc && val->GetUnit() == eCSSUnit_Calc) ||
+                 (unit == eUnit_Color &&
+                  nsCSSValue::IsNumericColorUnit(val->GetUnit())) ||
                  (unit == eUnit_ObjectPosition &&
                   val->GetUnit() == eCSSUnit_Array) ||
                  (unit == eUnit_URL && val->GetUnit() == eCSSUnit_URL) ||
                  unit == eUnit_DiscreteCSSValue,
                  "unexpected unit");
       aSpecifiedValue = *val;
       break;
     }
@@ -4331,17 +4489,18 @@ StyleAnimationValue::StyleAnimationValue
   mUnit = eUnit_Float;
   mValue.mFloat = aFloat;
   MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
 }
 
 StyleAnimationValue::StyleAnimationValue(nscolor aColor, ColorConstructorType)
 {
   mUnit = eUnit_Color;
-  mValue.mColor = aColor;
+  mValue.mCSSValue = new nsCSSValue();
+  mValue.mCSSValue->SetColorValue(aColor);
 }
 
 StyleAnimationValue&
 StyleAnimationValue::operator=(const StyleAnimationValue& aOther)
 {
   if (this == &aOther) {
     return *this;
   }
@@ -4364,20 +4523,18 @@ StyleAnimationValue::operator=(const Sty
     case eUnit_Coord:
       mValue.mCoord = aOther.mValue.mCoord;
       break;
     case eUnit_Percent:
     case eUnit_Float:
       mValue.mFloat = aOther.mValue.mFloat;
       MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
       break;
+    case eUnit_Calc:
     case eUnit_Color:
-      mValue.mColor = aOther.mValue.mColor;
-      break;
-    case eUnit_Calc:
     case eUnit_ObjectPosition:
     case eUnit_URL:
     case eUnit_DiscreteCSSValue:
       MOZ_ASSERT(IsCSSValueUnit(mUnit),
                  "This clause is for handling nsCSSValue-backed units");
       MOZ_ASSERT(aOther.mValue.mCSSValue, "values may not be null");
       mValue.mCSSValue = new nsCSSValue(*aOther.mValue.mCSSValue);
       break;
@@ -4489,17 +4646,18 @@ StyleAnimationValue::SetFloatValue(float
   MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
 }
 
 void
 StyleAnimationValue::SetColorValue(nscolor aColor)
 {
   FreeValue();
   mUnit = eUnit_Color;
-  mValue.mColor = aColor;
+  mValue.mCSSValue = new nsCSSValue();
+  mValue.mCSSValue->SetColorValue(aColor);
 }
 
 void
 StyleAnimationValue::SetCurrentColorValue()
 {
   FreeValue();
   mUnit = eUnit_CurrentColor;
 }
@@ -4643,19 +4801,18 @@ StyleAnimationValue::operator==(const St
     case eUnit_Visibility:
     case eUnit_Integer:
       return mValue.mInt == aOther.mValue.mInt;
     case eUnit_Coord:
       return mValue.mCoord == aOther.mValue.mCoord;
     case eUnit_Percent:
     case eUnit_Float:
       return mValue.mFloat == aOther.mValue.mFloat;
+    case eUnit_Calc:
     case eUnit_Color:
-      return mValue.mColor == aOther.mValue.mColor;
-    case eUnit_Calc:
     case eUnit_ObjectPosition:
     case eUnit_URL:
     case eUnit_DiscreteCSSValue:
       MOZ_ASSERT(IsCSSValueUnit(mUnit),
                  "This clause is for handling nsCSSValue-backed units");
       return *mValue.mCSSValue == *aOther.mValue.mCSSValue;
     case eUnit_CSSValuePair:
       return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair;
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -124,16 +124,37 @@ public:
    * positive.
    */
   static MOZ_MUST_USE bool
   AddWeighted(nsCSSPropertyID aProperty,
               double aCoeff1, const StyleAnimationValue& aValue1,
               double aCoeff2, const StyleAnimationValue& aValue2,
               StyleAnimationValue& aResultValue);
 
+  /**
+   * Accumulates |aValueToAccumulate| onto |aDest| |aCount| times.
+   * The result is stored in |aDest| on success.
+   *
+   * @param aDest              The base value to be accumulated.
+   * @param aValueToAccumulate The value to accumulate.
+   * @param aCount             The number of times to accumulate
+   *                           aValueToAccumulate.
+   * @return true on success, false on failure.
+   *
+   * NOTE: This function will work as a wrapper of StyleAnimationValue::Add()
+   * if |aProperty| isn't color or shadow or filter.  For these properties,
+   * this function may return a color value that at least one of its components
+   * has a value which is outside the range [0, 1] so that we can calculate
+   * plausible values as interpolation with the return value.
+   */
+  static MOZ_MUST_USE bool
+  Accumulate(nsCSSPropertyID aProperty, StyleAnimationValue& aDest,
+             const StyleAnimationValue& aValueToAccumulate,
+             uint64_t aCount);
+
   // Type-conversion methods
   // -----------------------
   /**
    * Creates a computed value for the given specified value
    * (property ID + string).  A style context is needed in case the
    * specified value depends on inherited style or on the values of other
    * properties.
    *
@@ -279,17 +300,18 @@ public:
     eUnit_None,
     eUnit_Enumerated,
     eUnit_Visibility, // special case for transitions (which converts
                       // Enumerated to Visibility as needed)
     eUnit_Integer,
     eUnit_Coord,
     eUnit_Percent,
     eUnit_Float,
-    eUnit_Color,
+    eUnit_Color, // nsCSSValue* (never null), always with an nscolor or
+                 // an nsCSSValueFloatColor
     eUnit_CurrentColor,
     eUnit_Calc, // nsCSSValue* (never null), always with a single
                 // calc() expression that's either length or length+percent
     eUnit_ObjectPosition, // nsCSSValue* (never null), always with a
                           // 4-entry nsCSSValue::Array
     eUnit_URL, // nsCSSValue* (never null), always with a css::URLValue
     eUnit_DiscreteCSSValue, // nsCSSValue* (never null)
     eUnit_CSSValuePair, // nsCSSValuePair* (never null)
@@ -306,17 +328,16 @@ public:
   };
 
 private:
   Unit mUnit;
   union {
     int32_t mInt;
     nscoord mCoord;
     float mFloat;
-    nscolor mColor;
     nsCSSValue* mCSSValue;
     nsCSSValuePair* mCSSValuePair;
     nsCSSValueTriplet* mCSSValueTriplet;
     nsCSSRect* mCSSRect;
     nsCSSValue::Array* mCSSValueArray;
     nsCSSValueList* mCSSValueList;
     nsCSSValueSharedList* mCSSValueSharedList;
     nsCSSValuePairList* mCSSValuePairList;
@@ -346,20 +367,16 @@ public:
   float GetPercentValue() const {
     NS_ASSERTION(mUnit == eUnit_Percent, "unit mismatch");
     return mValue.mFloat;
   }
   float GetFloatValue() const {
     NS_ASSERTION(mUnit == eUnit_Float, "unit mismatch");
     return mValue.mFloat;
   }
-  nscolor GetColorValue() const {
-    NS_ASSERTION(mUnit == eUnit_Color, "unit mismatch");
-    return mValue.mColor;
-  }
   nsCSSValue* GetCSSValueValue() const {
     NS_ASSERTION(IsCSSValueUnit(mUnit), "unit mismatch");
     return mValue.mCSSValue;
   }
   nsCSSValuePair* GetCSSValuePairValue() const {
     NS_ASSERTION(IsCSSValuePairUnit(mUnit), "unit mismatch");
     return mValue.mCSSValuePair;
   }
@@ -485,17 +502,18 @@ private:
     return static_cast<char16_t*>(aBuffer->Data());
   }
 
   static bool IsIntUnit(Unit aUnit) {
     return aUnit == eUnit_Enumerated || aUnit == eUnit_Visibility ||
            aUnit == eUnit_Integer;
   }
   static bool IsCSSValueUnit(Unit aUnit) {
-    return aUnit == eUnit_Calc ||
+    return aUnit == eUnit_Color ||
+           aUnit == eUnit_Calc ||
            aUnit == eUnit_ObjectPosition ||
            aUnit == eUnit_URL ||
            aUnit == eUnit_DiscreteCSSValue;
   }
   static bool IsCSSValuePairUnit(Unit aUnit) {
     return aUnit == eUnit_CSSValuePair;
   }
   static bool IsCSSValueTripletUnit(Unit aUnit) {
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -777,17 +777,17 @@ CSSAnimationBuilder::BuildAnimationFrame
       }
     }
 
     // If we have a keyframe at the same offset with the same timing
     // function we should merge our (unique) values into it.
     // Otherwise, we should update the existing keyframe with only the
     // unique properties.
     //
-    // Bug 1216843: We should also match composite modes here.
+    // Bug 1293490: We should also match composite modes here.
     Keyframe* existingKeyframe = nullptr;
     // Don't bother searching for an existing keyframe if we don't
     // have anything to contribute to it.
     if (!uniquePropertyValues.IsEmpty()) {
       for (size_t i = keyframeIdx; i < keyframes.Length(); i++) {
         Keyframe& kf = keyframes[i];
         if (kf.mOffset.value() != currentOffset) {
           break;
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -1479,27 +1479,28 @@ ExtractAnimationValue(nsCSSPropertyID aP
 
 static nscolor
 ExtractColor(nsCSSPropertyID aProperty,
              nsStyleContext *aStyleContext)
 {
   StyleAnimationValue val;
   ExtractAnimationValue(aProperty, aStyleContext, val);
   return val.GetUnit() == StyleAnimationValue::eUnit_CurrentColor
-    ? aStyleContext->StyleColor()->mColor : val.GetColorValue();
+    ? aStyleContext->StyleColor()->mColor
+    : val.GetCSSValueValue()->GetColorValue();
 }
 
 static nscolor
 ExtractColorLenient(nsCSSPropertyID aProperty,
                     nsStyleContext *aStyleContext)
 {
   StyleAnimationValue val;
   ExtractAnimationValue(aProperty, aStyleContext, val);
   if (val.GetUnit() == StyleAnimationValue::eUnit_Color) {
-    return val.GetColorValue();
+    return val.GetCSSValueValue()->GetColorValue();
   } else if (val.GetUnit() == StyleAnimationValue::eUnit_CurrentColor) {
     return aStyleContext->StyleColor()->mColor;
   }
   return NS_RGBA(0, 0, 0, 0);
 }
 
 struct ColorIndexSet {
   uint8_t colorIndex, alphaIndex;
--- a/old-configure.in
+++ b/old-configure.in
@@ -236,19 +236,16 @@ else
 fi
 
 if test -n "$MOZ_WINCONSOLE"; then
     AC_DEFINE(MOZ_WINCONSOLE)
 fi
 
 MOZ_TOOL_VARIABLES
 
-AC_PROG_CPP
-AC_PROG_CXXCPP
-
 dnl ========================================================
 dnl Special win32 checks
 dnl ========================================================
 
 WINVER=502
 
 case "$target" in
 *-mingw*)
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -11405,20 +11405,16 @@
         "path": "IndexedDB/idbcursor-advance.htm",
         "url": "/IndexedDB/idbcursor-advance.htm"
       },
       {
         "path": "IndexedDB/idbcursor-continue.htm",
         "url": "/IndexedDB/idbcursor-continue.htm"
       },
       {
-        "path": "IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm",
-        "url": "/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm"
-      },
-      {
         "path": "IndexedDB/idbcursor-continuePrimaryKey.htm",
         "url": "/IndexedDB/idbcursor-continuePrimaryKey.htm"
       },
       {
         "path": "IndexedDB/idbcursor-direction-index-keyrange.htm",
         "url": "/IndexedDB/idbcursor-direction-index-keyrange.htm"
       },
       {
@@ -35717,16 +35713,21 @@
         "path": "workers/semantics/xhr/005.html",
         "url": "/workers/semantics/xhr/005.html"
       },
       {
         "path": "workers/semantics/xhr/006.html",
         "url": "/workers/semantics/xhr/006.html"
       },
       {
+        "path": "IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm",
+        "timeout": "long",
+        "url": "/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm"
+      },
+      {
         "path": "IndexedDB/idbdatabase_createObjectStore10-1000ends.htm",
         "timeout": "long",
         "url": "/IndexedDB/idbdatabase_createObjectStore10-1000ends.htm"
       },
       {
         "path": "IndexedDB/idbdatabase_createObjectStore8-parameters.htm",
         "timeout": "long",
         "url": "/IndexedDB/idbdatabase_createObjectStore8-parameters.htm"
@@ -37879,16 +37880,22 @@
           }
         ],
         "web-animations/interfaces/Animation/effect.html": [
           {
             "path": "web-animations/interfaces/Animation/effect.html",
             "url": "/web-animations/interfaces/Animation/effect.html"
           }
         ],
+        "web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
+          {
+            "path": "web-animations/interfaces/KeyframeEffect/iterationComposite.html",
+            "url": "/web-animations/interfaces/KeyframeEffect/iterationComposite.html"
+          }
+        ],
         "web-animations/interfaces/KeyframeEffect/spacing.html": [
           {
             "path": "web-animations/interfaces/KeyframeEffect/spacing.html",
             "url": "/web-animations/interfaces/KeyframeEffect/spacing.html"
           }
         ],
         "web-animations/timing-model/animation-effects/phases-and-states.html": [
           {
--- a/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
@@ -11,28 +11,28 @@
   [Element.animate() accepts a one property two value property-indexed keyframes specification where the first value is invalid]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [Element.animate() accepts a one property two value property-indexed keyframes specification where the second value is invalid]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
-  [Element.animate() accepts a keyframe sequence with different composite values, but the same composite value for a given offset]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
-
   [Element.animate() accepts a one property one keyframe sequence]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [Element.animate() accepts a single keyframe sequence with omitted offsets]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
+  [Element.animate() accepts a keyframe sequence with different composite values, but the same composite value for a given offset]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
+
   [Element.animate() accepts a two property keyframe sequence where one property is missing from the first keyframe]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [Element.animate() accepts a two property keyframe sequence where one property is missing from the last keyframe]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/constructor.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/constructor.html.ini
@@ -1,25 +1,21 @@
 [constructor.html]
   type: testharness
   [composite values are parsed correctly when passed to the KeyframeEffectReadOnly constructor in property-indexed keyframes]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
   [composite values are parsed correctly when passed to the KeyframeEffectReadOnly constructor in regular keyframes]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
   [composite values are parsed correctly when passed to the KeyframeEffectReadOnly constructor in KeyframeTimingOptions]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
-
-  [a KeyframeEffectReadOnly can be constructed with a keyframe sequence with different composite values, but the same composite value for a given offset]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [a KeyframeEffectReadOnly can be constructed with a one property one value property-indexed keyframes specification]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [a KeyframeEffectReadOnly constructed with a one property one value property-indexed keyframes specification roundtrips]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
@@ -59,16 +55,20 @@
   [a KeyframeEffectReadOnly can be constructed with a single keyframe sequence with omitted offsets]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [a KeyframeEffectReadOnly constructed with a single keyframe sequence with omitted offsets roundtrips]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
+  [a KeyframeEffectReadOnly can be constructed with a keyframe sequence with different composite values, but the same composite value for a given offset]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
+
   [a KeyframeEffectReadOnly can be constructed with a two property keyframe sequence where one property is missing from the first keyframe]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [a KeyframeEffectReadOnly constructed with a two property keyframe sequence where one property is missing from the first keyframe roundtrips]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/setKeyframes.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/setKeyframes.html.ini
@@ -11,29 +11,29 @@
   [Keyframes can be replaced with a one property two value property-indexed keyframes specification where the first value is invalid]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [Keyframes can be replaced with a one property two value property-indexed keyframes specification where the second value is invalid]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
+  [Keyframes can be replaced with a one property one keyframe sequence]
+    expected: FAIL
+
+  [Keyframes can be replaced with a single keyframe sequence with omitted offsets]
+    expected: FAIL
+
   [Keyframes can be replaced with a keyframe sequence with different composite values, but the same composite value for a given offset]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216843
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
   [Keyframes can be replaced with a two property keyframe sequence where one property is missing from the first keyframe]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
   [Keyframes can be replaced with a two property keyframe sequence where one property is missing from the last keyframe]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
 
-  [Keyframes can be replaced with a one property one keyframe sequence]
-    expected: FAIL
-
-  [Keyframes can be replaced with a single keyframe sequence with omitted offsets]
-    expected: FAIL
-
   [Keyframes can be replaced with a single keyframe sequence with string offset]
     expected: FAIL
 
--- a/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm
+++ b/testing/web-platform/tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm
@@ -1,10 +1,11 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
+<meta name="timeout" content="long">
 <title>IDBCursor.continuePrimaryKey() - Exception Orders </title>
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <link rel="help" href="http://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="support.js"></script>
 
 <script>
--- a/testing/web-platform/tests/XMLHttpRequest/responsexml-document-properties.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/responsexml-document-properties.htm
@@ -5,17 +5,17 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-responsexml-attribute" data-tested-assertations="following::ol[1]/li[4]" />
     <link rel="help" href="https://xhr.spec.whatwg.org/#document-response-entity-body" data-tested-assertations="following::ol[1]/li[6] following::ol[1]/li[7] following::ol[1]/li[8] following::ol[1]/li[10]" />
   </head>
   <body>
     <div id="log"></div>
     <script>
-      var timePreXHR = Math.floor(new Date().getTime() / 1000);
+      var timePreXHR = Math.floor(new Date().getTime(new Date().getTime() - 3000) / 1000); // three seconds ago, in case there's clock drift
       var client = new XMLHttpRequest()
       client.open("GET", "resources/well-formed.xml", false)
       client.send(null)
       var expected = {
         domain:undefined,
         URL:location.href.replace(/[^/]*$/, 'resources/well-formed.xml'),
         documentURI:location.href.replace(/[^/]*$/, 'resources/well-formed.xml'),
         referrer:'',
@@ -40,17 +40,17 @@
       function runTest(name, value){
         test(function(){
           assert_equals(client.responseXML[name], value)
         }, name)
       }
 
       test(function() {
         var lastModified = Math.floor(new Date(client.responseXML.lastModified).getTime() / 1000);
-        var now = Math.floor(new Date().getTime(new Date().getTime() + 2000) / 1000); // two seconds from now, in case there's clock drift
+        var now = Math.floor(new Date().getTime(new Date().getTime() + 3000) / 1000); // three seconds from now, in case there's clock drift
         assert_greater_than_equal(lastModified, timePreXHR);
         assert_less_than_equal(lastModified, now);
       }, 'lastModified set to time of response if no HTTP header provided')
 
       test(function() {
         var client2 = new XMLHttpRequest()
         client2.open("GET", "resources/last-modified.py", false)
         client2.send(null)
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html
@@ -0,0 +1,693 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.iterationComposite tests</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#effect-accumulation-section">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../../testcommon.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ marginLeft: ['0px', '10px'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '5px',
+    'Animated margin-left style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).marginLeft, '20px',
+    'Animated margin-left style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '25px',
+    'Animated margin-left style at 50s of the third iteration');
+}, 'iterationComposite of <length> type animation');
+
+test(function(t) {
+  var parent = createDiv(t);
+  parent.style.width = '100px';
+  var div = createDiv(t);
+  parent.appendChild(div);
+
+  var anim =
+    div.animate({ width: ['0%', '50%'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width, '25px',
+    'Animated width style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).width, '100px',
+    'Animated width style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width, '125px',
+    'Animated width style at 50s of the third iteration');
+}, 'iterationComposite of <percentage> type animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ color: ['rgb(0, 0, 0)', 'rgb(120, 120, 120)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)',
+    'Animated color style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)',
+    'Animated color style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)',
+    'Animated color style at 50s of the third iteration');
+}, 'iterationComposite of <color> type animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ color: ['rgb(0, 120, 0)', 'rgb(60, 60, 60)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)',
+    'Animated color style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)',
+    'Animated color style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  // The green color is (240 + 180) / 2 = 210
+  assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)',
+    'Animated color style at 50s of the third iteration');
+}, 'iterationComposite of <color> type animation that green component is ' +
+   'decreasing');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ flexGrow: [0, 10] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).flexGrow, '5',
+    'Animated flex-grow style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).flexGrow, '20',
+    'Animated flex-grow style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).flexGrow, '25',
+    'Animated flex-grow style at 50s of the third iteration');
+}, 'iterationComposite of <number> type animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  div.style.position = 'absolute';
+  var anim =
+    div.animate({ clip: ['rect(0px, 0px, 0px, 0px)',
+                         'rect(10px, 10px, 10px, 10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)',
+    'Animated clip style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)',
+    'Animated clip style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)',
+    'Animated clip style at 50s of the third iteration');
+}, 'iterationComposite of <shape> type animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ width: ['calc(0vw + 0px)', 'calc(0vw + 10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width, '5px',
+    'Animated calc width style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).width, '20px',
+    'Animated calc width style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width, '25px',
+    'Animated calc width style at 50s of the third iteration');
+}, 'iterationComposite of <calc()> value animation');
+
+test(function(t) {
+  var parent = createDiv(t);
+  parent.style.width = '100px';
+  var div = createDiv(t);
+  parent.appendChild(div);
+
+  var anim =
+    div.animate({ width: ['calc(0% + 0px)', 'calc(10% + 10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width, '10px',
+    // 100px * 5% + 5px
+    'Animated calc width style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).width,
+    '40px', // 100px * (10% + 10%) + (10px + 10px)
+    'Animated calc width style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).width,
+    '50px', // (40px + 60px) / 2
+    'Animated calc width style at 50s of the third iteration');
+}, 'iterationComposite of <calc()> value animation that the values can\'t' +
+   'be reduced');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ opacity: [0, 0.4] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).opacity, '0.2',
+    'Animated opacity style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).opacity, '0.8',
+    'Animated opacity style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5
+    'Animated opacity style at 50s of the third iteration');
+}, 'iterationComposite of opacity animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ boxShadow: ['rgb(0, 0, 0) 0px 0px 0px 0px',
+                              'rgb(120, 120, 120) 10px 10px 10px 0px'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).boxShadow,
+    'rgb(60, 60, 60) 5px 5px 5px 0px',
+    'Animated box-shadow style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).boxShadow,
+    'rgb(240, 240, 240) 20px 20px 20px 0px',
+    'Animated box-shadow style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).boxShadow,
+    'rgb(255, 255, 255) 25px 25px 25px 0px',
+    'Animated box-shadow style at 50s of the third iteration');
+}, 'iterationComposite of box-shadow animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['blur(0px)', 'blur(10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter, 'blur(5px)',
+    'Animated filter blur style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter, 'blur(20px)',
+    'Animated filter blur style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter, 'blur(25px)',
+    'Animated filter blur style at 50s of the third iteration');
+}, 'iterationComposite of filter blur animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['brightness(1)',
+                           'brightness(180%)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(1.4)',
+    'Animated filter brightness style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8)
+    'Animated filter brightness style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5
+    'Animated filter brightness style at 50s of the third iteration');
+}, 'iterationComposite of filter brightness for different unit animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['brightness(0)',
+                           'brightness(1)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(0.5)',
+    'Animated filter brightness style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(0)', // brightness(1) is an identity element, not accumulated.
+    'Animated filter brightness style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(0.5)', // brightness(1) is an identity element, not accumulated.
+    'Animated filter brightness style at 50s of the third iteration');
+}, 'iterationComposite of filter brightness animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['drop-shadow(rgb(0, 0, 0) 0px 0px 0px)',
+                           'drop-shadow(rgb(120, 120, 120) 10px 10px 10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)',
+    'Animated filter drop-shadow style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)',
+    'Animated filter drop-shadow style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)',
+    'Animated filter drop-shadow style at 50s of the third iteration');
+}, 'iterationComposite of filter drop-shadow animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['brightness(1) contrast(1)',
+                           'brightness(2) contrast(2)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(1.5) contrast(1.5)',
+    'Animated filter list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(3) contrast(3)',
+    'Animated filter list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'brightness(3.5) contrast(3.5)',
+    'Animated filter list at 50s of the third iteration');
+}, 'iterationComposite of same filter list animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['brightness(1) contrast(1)',
+                           'contrast(2) brightness(2)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'contrast(2) brightness(2)', // discrete
+    'Animated filter list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    // We can't accumulate 'contrast(2) brightness(2)' onto
+    // the first list 'brightness(1) contrast(1)' because of
+    // mismatch of the order.
+    'brightness(1) contrast(1)',
+    'Animated filter list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    // We *can* accumulate 'contrast(2) brightness(2)' onto
+    // the same list 'contrast(2) brightness(2)' here.
+    'contrast(4) brightness(4)', // discrete
+    'Animated filter list at 50s of the third iteration');
+}, 'iterationComposite of discrete filter list because of mismatch ' +
+   'of the order');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ filter: ['sepia(0)',
+                           'sepia(1) contrast(2)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'sepia(0.5) contrast(1.5)',
+    'Animated filter list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).filter,
+    'sepia(2) contrast(3)',
+    'Animated filter list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).filter,
+    'sepia(2.5) contrast(3.5)',
+    'Animated filter list at 50s of the third iteration');
+}, 'iterationComposite of different length filter list animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['rotate(0deg)', 'rotate(180deg)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg)
+    'Animated transform(rotate) style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg)
+    'Animated transform(rotate) style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg)
+    'Animated transform(rotate) style at 50s of the third iteration');
+}, 'iterationComposite of transform(rotate) animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['scale(0)', 'scale(1)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5)
+    'Animated transform(scale) style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element,
+                                // not accumulated.
+    'Animated transform(scale) style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity
+                                    // element, not accumulated.
+    'Animated transform(scale) style at 50s of the third iteration');
+}, 'iterationComposite of transform: [ scale(0), scale(1) ] animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['scale(1)', 'scale(2)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5)
+    'Animated transform(scale) style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1))
+    'Animated transform(scale) style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5
+    'Animated transform(scale) style at 50s of the third iteration');
+}, 'iterationComposite of transform: [ scale(1), scale(2) ] animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['scale(0)', 'scale(2)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 0, 0)', // scale(1)
+    'Animated transform(scale) style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2)
+    'Animated transform(scale) style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5
+    'Animated transform(scale) style at 50s of the third iteration');
+}, 'iterationComposite of transform: scale(2) animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['rotate(0deg) translateX(0px)',
+                              'rotate(180deg) translateX(10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px)
+    'Animated transform list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px)
+    'Animated transform list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px)
+    'Animated transform list at 50s of the third iteration');
+}, 'iterationComposite of transform list animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  // The transform list whose order is mismatched is compounded,
+  // so below animation is the same as;
+  // from matrix(2, 0, 0, 2, 0, 0) to matrix(3, 0, 0, 3, 30, 0)
+  var anim =
+    div.animate({ transform: ['translateX(0px) scale(2)',
+                              'scale(3) translateX(10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(2.5, 0, 0, 2.5, 15, 0)', // scale(2.5) (0px + 30px*2) / 2
+    'Animated transform list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(4, 0, 0, 4, 60, 0)', // scale(2+(3-2)*2) (0px + 30px*2)
+    'Animated transform list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(5.5, 0, 0, 5.5, 135, 0)', // scale(4+7)/2 (60px + 210px)
+    'Animated transform list at 50s of the third iteration');
+}, 'iterationComposite of transform list animation whose order is mismatched');
+
+test(function(t) {
+  var div = createDiv(t);
+  // Even if each transform list does not have functions which exist in
+  // other pair of the list, we don't fill any missing functions at all,
+  // it's just computed as compounded matrices
+  // Below animation is the same as;
+  // from matrix(1, 0, 0, 1, 0, 0) to matrix(2, 0, 0, 2, 20, 0)
+  var anim =
+    div.animate({ transform: ['translateX(0px)',
+                              'scale(2) translateX(10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1.5, 0, 0, 1.5, 10, 0)', // scale(1.5) (0px + 10px*2) / 2
+    'Animated transform list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(3, 0, 0, 3, 40, 0)', // scale(1+(2-1)*2) (0px + 20px*2)
+    'Animated transform list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(3.5, 0, 0, 3.5, 80, 0)', // scale(3+4)/2 (40px + 20px)
+    'Animated transform list at 50s of the third iteration');
+}, 'iterationComposite of transform list animation whose order is mismatched');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ marginLeft: ['10px', '20px'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '15px',
+    'Animated margin-left style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px
+    'Animated margin-left style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5
+    'Animated margin-left style at 50s of the third iteration');
+}, 'iterationComposite starts with non-zero value animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ marginLeft: ['10px', '-10px'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft,
+    '0px',
+    'Animated margin-left style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).marginLeft,
+    '-10px', // 10px + -10px + -10px
+    'Animated margin-left style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft,
+    '-20px', // (-10px + -30px) * 0.5
+    'Animated margin-left style at 50s of the third iteration');
+}, 'iterationComposite with negative final value animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ marginLeft: ['0px', '10px'] },
+                         { duration: 100 * MS_PER_SEC,
+                           easing: 'linear',
+                           iterations: 10,
+                           iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime =
+    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '25px',
+    'Animated style at 50s of the third iteration');
+
+  anim.effect.iterationComposite = 'replace';
+  assert_equals(getComputedStyle(div).marginLeft, '5px',
+    'Animated style at 50s of the third iteration');
+
+  anim.effect.iterationComposite = 'accumulate';
+  assert_equals(getComputedStyle(div).marginLeft, '25px',
+    'Animated style at 50s of the third iteration');
+}, 'interationComposite changes');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ marginLeft: ['0px', '10px'] },
+                         { duration: 100 * MS_PER_SEC,
+                           easing: 'linear',
+                           iterations: 10,
+                           iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime =
+    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).marginLeft, '25px',
+    'Animated style at 50s of the third iteration');
+
+  // double its duration.
+  anim.effect.timing.duration = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).marginLeft, '12.5px',
+    'Animated style at 25s of the first iteration');
+
+  // half of original.
+  anim.effect.timing.duration = anim.effect.timing.duration / 4;
+  assert_equals(getComputedStyle(div).marginLeft, '50px',
+      'Animated style at 50s of the fourth iteration');
+}, 'duration changes with iterationComposite(accumulate)');
+
+</script>