Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 28 Jul 2017 17:54:59 -0700
changeset 422995 a09d4e4b31bc001d2a050fa311d32fca37b28db4
parent 422994 03533ecbc1af51dd87edf1419b132a5a254f43a1 (current diff)
parent 422851 ec666e910442ae55686160c1bd0c93b00a08dead (diff)
child 422996 58ec9fceb4acff9dfbb798d9cf9a859af6295521
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound, a=merge MozReview-Commit-ID: KcxntLtRalL
browser/extensions/formautofill/locale/en-US/formautofill.properties
browser/extensions/formautofill/locale/jar.mn
browser/extensions/formautofill/locale/moz.build
taskcluster/mach_commands.py
taskcluster/taskgraph/actions/__init__.py
taskcluster/taskgraph/actions/add-new-jobs.py
taskcluster/taskgraph/actions/registry.py
taskcluster/taskgraph/actions/retrigger.py
taskcluster/taskgraph/actions/run_missing_tests.py
taskcluster/taskgraph/actions/test-retrigger-action.py
taskcluster/taskgraph/actions/util.py
taskcluster/taskgraph/decision.py
taskcluster/taskgraph/util/taskcluster.py
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -162,16 +162,21 @@
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
           <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
 
+        <!-- Advisory -->
+        <div id="advisoryDesc">
+          <p id="advisoryDescText">&safeb.palm.advisory.desc;</p>
+        </div>
+
         <!-- Action buttons -->
         <div id="buttons" class="button-container">
           <!-- Commands handled in browser.js -->
           <button id="getMeOutButton" class="primary">&safeb.palm.accept.label;</button>
           <div class="button-spacer"></div>
           <button id="reportButton">&safeb.palm.reportPage.label;</button>
         </div>
       </div>
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1175,17 +1175,17 @@ toolbarpaletteitem[place="palette"] > #d
   -moz-window-transform-origin: 20px bottom;
 }
 
 #BMB_bookmarksPopup[arrowposition="before_end"]:-moz-locale-dir(ltr),
 #BMB_bookmarksPopup[arrowposition="before_start"]:-moz-locale-dir(rtl) {
   -moz-window-transform-origin: calc(100% - 20px) bottom;
 }
 
-%else
+%elifndef MOZ_WIDGET_GTK
 
 #BMB_bookmarksPopup {
   transform: scale(.4);
   opacity: 0;
   transition-property: transform, opacity;
   transition-duration: 0.15s;
   transition-timing-function: ease-out;
 }
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -310,21 +310,77 @@ function getSiteBlockedErrorDetails(docS
       blockedInfo = { list: classifiedChannel.matchedList,
                       provider: classifiedChannel.matchedProvider,
                       uri: reportUri.asciiSpec };
     }
   }
   return blockedInfo;
 }
 
-addMessageListener("DeceptiveBlockedDetails", (message) => {
-  sendAsyncMessage("DeceptiveBlockedDetails:Result", {
-    blockedInfo: getSiteBlockedErrorDetails(docShell),
-  });
-});
+var AboutBlockedSiteListener = {
+  init(chromeGlobal) {
+    addMessageListener("DeceptiveBlockedDetails", this);
+    chromeGlobal.addEventListener("AboutBlockedLoaded", this, false, true);
+  },
+
+  get isBlockedSite() {
+    return content.document.documentURI.startsWith("about:blocked");
+  },
+
+  receiveMessage(msg) {
+    if (!this.isBlockedSite) {
+      return;
+    }
+
+    if (msg.name == "DeceptiveBlockedDetails") {
+      sendAsyncMessage("DeceptiveBlockedDetails:Result", {
+        blockedInfo: getSiteBlockedErrorDetails(docShell),
+      });
+    }
+  },
+
+  handleEvent(aEvent) {
+    if (!this.isBlockedSite) {
+      return;
+    }
+
+    if (aEvent.type != "AboutBlockedLoaded") {
+      return;
+    }
+
+    let provider = "";
+    if (docShell.failedChannel) {
+      let classifiedChannel = docShell.failedChannel.
+                              QueryInterface(Ci.nsIClassifiedChannel);
+      if (classifiedChannel) {
+        provider = classifiedChannel.matchedProvider;
+      }
+    }
+
+    let advisoryUrl = Services.prefs.getCharPref(
+      "browser.safebrowsing.provider." + provider + ".advisoryURL", "");
+    if (!advisoryUrl) {
+      let el = content.document.getElementById("advisoryDesc");
+      el.remove();
+      return;
+    }
+
+    let advisoryLinkText = Services.prefs.getCharPref(
+      "browser.safebrowsing.provider." + provider + ".advisoryName", "");
+    if (!advisoryLinkText) {
+      let el = content.document.getElementById("advisoryDesc");
+      el.remove();
+      return;
+    }
+
+    let anchorEl = content.document.getElementById("advisory_provider");
+    anchorEl.setAttribute("href", advisoryUrl);
+    anchorEl.textContent = advisoryLinkText;
+  },
+}
 
 var AboutNetAndCertErrorListener = {
   init(chromeGlobal) {
     addMessageListener("CertErrorDetails", this);
     addMessageListener("Browser:CaptivePortalFreed", this);
     chromeGlobal.addEventListener("AboutNetErrorLoad", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorOpenCaptivePortal", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorSetAutomatic", this, false, true);
@@ -512,17 +568,17 @@ var AboutNetAndCertErrorListener = {
         securityInfo: getSerializedSecurityInfo(docShell),
       });
 
     }
   },
 }
 
 AboutNetAndCertErrorListener.init(this);
-
+AboutBlockedSiteListener.init(this);
 
 var ClickEventHandler = {
   init: function init() {
     Cc["@mozilla.org/eventlistenerservice;1"]
       .getService(Ci.nsIEventListenerService)
       .addSystemEventListener(global, "click", this, true);
   },
 
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -341,16 +341,25 @@
             <row id="security-identity-verifier-row">
               <label id="security-identity-verifier-label"
                      class="fieldLabel"
                      value="&securityView.identity.verifier;"
                      control="security-identity-verifier-value"/>
               <textbox id="security-identity-verifier-value"
                        class="fieldValue" readonly="true" />
             </row>
+            <!-- Certificate Validity -->
+            <row id="security-identity-validity-row">
+              <label id="security-identity-validity-label"
+                     class="fieldLabel"
+                     value="&securityView.identity.validity;"
+                     control="security-identity-validity-value"/>
+              <textbox id="security-identity-validity-value"
+                       class="fieldValue" readonly="true" />
+            </row>
           </rows>
         </grid>
         <spacer flex="1"/>
         <!-- Cert button -->
         <hbox id="security-view-cert-box" pack="end">
           <button id="security-view-cert" label="&securityView.certView;"
                   accesskey="&securityView.accesskey;"
                   oncommand="security.viewCert();"/>
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -190,18 +190,20 @@ function securityOnLoad(uri, windowInfo)
   }
   document.getElementById("securityTab").hidden = false;
 
   const pageInfoBundle = document.getElementById("pageinfobundle");
 
   /* Set Identity section text */
   setText("security-identity-domain-value", info.hostName);
 
-  var owner, verifier;
+  var owner, verifier, validity;
   if (info.cert && !info.isBroken) {
+    validity = info.cert.validity.notAfterLocalDay;
+
     // Try to pull out meaningful values.  Technically these fields are optional
     // so we'll employ fallbacks where appropriate.  The EV spec states that Org
     // fields must be specified for subject and issuer so that case is simpler.
     if (info.isEV) {
       owner = info.cert.organization;
       verifier = security.mapIssuerOrganization(info.cAName);
     } else {
       // Technically, a non-EV cert might specify an owner in the O field or not,
@@ -217,16 +219,21 @@ function securityOnLoad(uri, windowInfo)
   } else {
     // We don't have valid identity credentials.
     owner = pageInfoBundle.getString("securityNoOwner");
     verifier = pageInfoBundle.getString("notset");
   }
 
   setText("security-identity-owner-value", owner);
   setText("security-identity-verifier-value", verifier);
+  if (validity) {
+    setText("security-identity-validity-value", validity);
+  } else {
+    document.getElementById("security-identity-validity-row").hidden = true;
+  }
 
   /* Manage the View Cert button*/
   var viewCert = document.getElementById("security-view-cert");
   if (info.cert) {
     security._cert = info.cert;
     viewCert.collapsed = false;
   } else
     viewCert.collapsed = true;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -525,26 +525,31 @@
             return false;
           }
 
           // Specially handle about:blank as local
           if (aURI.path === "blank") {
             return true;
           }
 
-          // Use the passed in resolvedURI if we have one
-          const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2(
-            aURI,
-            null, // loadingNode
-            Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal
-            null, // triggeringPrincipal
-            Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, // securityFlags
-            Ci.nsIContentPolicy.TYPE_OTHER // contentPolicyType
-          ).URI;
-          return resolvedURI.schemeIs("jar") || resolvedURI.schemeIs("file");
+          try {
+            // Use the passed in resolvedURI if we have one
+            const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2(
+              aURI,
+              null, // loadingNode
+              Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal
+              null, // triggeringPrincipal
+              Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, // securityFlags
+              Ci.nsIContentPolicy.TYPE_OTHER // contentPolicyType
+            ).URI;
+            return resolvedURI.schemeIs("jar") || resolvedURI.schemeIs("file");
+          } catch (ex) {
+            // aURI might be invalid.
+            return false;
+          }
         ]]></body>
       </method>
 
       <!-- A web progress listener object definition for a given tab. -->
       <method name="mTabProgressListener">
         <parameter name="aTab"/>
         <parameter name="aBrowser"/>
         <parameter name="aStartsBlank"/>
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -25,16 +25,20 @@ let gWhitelist = [{
     key: "certerror.wrongSystemTime2",
     type: "single-quote"
   }, {
     file: "netError.dtd",
     key: "certerror.wrongSystemTimeWithoutReference",
     type: "single-quote"
   }, {
     file: "phishing-afterload-warning-message.dtd",
+    key: "safeb.palm.advisory.desc",
+    type: "single-quote"
+  }, {
+    file: "phishing-afterload-warning-message.dtd",
     key: "safeb.blocked.malwarePage.shortDesc",
     type: "single-quote"
   }, {
     file: "phishing-afterload-warning-message.dtd",
     key: "safeb.blocked.unwantedPage.shortDesc",
     type: "single-quote"
   }, {
     file: "phishing-afterload-warning-message.dtd",
--- a/browser/base/content/test/tabs/browser_isLocalAboutURI.js
+++ b/browser/base/content/test/tabs/browser_isLocalAboutURI.js
@@ -6,23 +6,29 @@
 /**
  * Unit tests for tabbrowser._isLocalAboutURI to make sure it returns the
  * appropriate values for various URIs as well as optional resolved URI.
  */
 
 add_task(function test_URI() {
   const check = (spec, expect, description) => {
     const URI = Services.io.newURI(spec);
-    is(gBrowser._isLocalAboutURI(URI), expect, description);
+    try {
+      is(gBrowser._isLocalAboutURI(URI), expect, description);
+    } catch (ex) {
+      ok(false, "_isLocalAboutURI should not throw");
+    }
   };
   check("https://www.mozilla.org/", false, "https is not about");
   check("http://www.mozilla.org/", false, "http is not about");
   check("about:blank", true, "about:blank is local");
   check("about:about", true, "about:about is local");
   check("about:newtab", true, "about:newtab is local");
+  check("about:random-invalid-uri", false,
+        "about:random-invalid-uri is invalid but should not throw");
 });
 
 add_task(function test_URI_with_resolved() {
   const check = (spec, resolvedSpec, expect, description) => {
     const URI = Services.io.newURI(spec);
     const resolvedURI = Services.io.newURI(resolvedSpec);
     is(gBrowser._isLocalAboutURI(URI, resolvedURI), expect, description);
   };
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -219,17 +219,18 @@ AboutRedirector::GetURIFlags(nsIURI *aUR
 
       // Once ActivityStream is fully rolled out and we've removed Tiles,
       // this special case can go away and the flag can just become part
       // of the normal about:newtab entry in kRedirMap.
       if (name.EqualsLiteral("newtab")) {
         if (sActivityStreamEnabled) {
           *result = redir.flags |
             nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
-            nsIAboutModule::ENABLE_INDEXED_DB;
+            nsIAboutModule::ENABLE_INDEXED_DB |
+            nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
           return NS_OK;
         }
       }
 
       *result = redir.flags;
       return NS_OK;
     }
   }
--- a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js
@@ -151,17 +151,19 @@ add_task(async function test_aboutURL() 
       let flags = am.getURIFlags(uri);
 
       // We load pages with URI_SAFE_FOR_UNTRUSTED_CONTENT set, this means they
       // are not loaded with System Principal but with codebase principal.
       // Also we skip pages with HIDE_FROM_ABOUTABOUT, some of them may have
       // errors while loading.
       if ((flags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) &&
           !(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) &&
-          networkURLs.indexOf(aboutType) == -1) {
+          networkURLs.indexOf(aboutType) == -1 &&
+          // Exclude about:newtab see Bug 1021667
+          aboutType !== "newtab") {
         aboutURLs.push(aboutType);
       }
     } catch (e) {
       // getService might have thrown if the component doesn't actually
       // implement nsIAboutModule
     }
   }
 
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -724,21 +724,21 @@ var gCookiesWindow = {
     } else {
       this._cm.removeAll();
     }
     this._updateRemoveAllButton();
     this.focusFilterBox();
   },
 
   onCookieKeyPress(aEvent) {
-    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
+    if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
+        (AppConstants.platform == "macosx" &&
+        aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
       this.deleteCookie();
-    } else if (AppConstants.platform == "macosx" &&
-               aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
-      this.deleteCookie();
+      aEvent.preventDefault();
     }
   },
 
   _lastSortProperty: "",
   _lastSortAscending: false,
   sort(aProperty) {
     var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
     // Sort the Non-Filtered Host Collections
--- a/browser/components/preferences/in-content-new/main.js
+++ b/browser/components/preferences/in-content-new/main.js
@@ -187,19 +187,19 @@ var gMainPane = {
     let performanceSettingsUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "performance";
     performanceSettingsLink.setAttribute("href", performanceSettingsUrl);
 
     this.updateDefaultPerformanceSettingsPref();
 
     let defaultPerformancePref =
       document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
     defaultPerformancePref.addEventListener("change", () => {
-      this.updatePerformanceSettingsBox();
+      this.updatePerformanceSettingsBox({duringChangeEvent: true});
     });
-    this.updatePerformanceSettingsBox();
+    this.updatePerformanceSettingsBox({duringChangeEvent: false});
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
 
     this.updateBrowserStartupLastSession();
 
     if (AppConstants.platform == "win") {
@@ -1109,33 +1109,34 @@ var gMainPane = {
     let processCountPref = document.getElementById("dom.ipc.processCount");
     let accelerationPref = document.getElementById("layers.acceleration.disabled");
     if (processCountPref.value != processCountPref.defaultValue ||
         accelerationPref.value != accelerationPref.defaultValue) {
       defaultPerformancePref.value = false;
     }
   },
 
-  updatePerformanceSettingsBox() {
+  updatePerformanceSettingsBox({duringChangeEvent}) {
     let defaultPerformancePref =
       document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
     let performanceSettings = document.getElementById("performanceSettings");
     let processCountPref = document.getElementById("dom.ipc.processCount");
     if (defaultPerformancePref.value) {
       let accelerationPref = document.getElementById("layers.acceleration.disabled");
       // Unset the value so process count will be decided by e10s rollout.
       processCountPref.value = processCountPref.defaultValue;
       accelerationPref.value = accelerationPref.defaultValue;
       performanceSettings.hidden = true;
     } else {
       let e10sRolloutProcessCountPref =
         document.getElementById("dom.ipc.processCount.web");
       // Take the e10s rollout value as the default value (if it exists),
       // but don't overwrite the user set value.
-      if (e10sRolloutProcessCountPref.value &&
+      if (duringChangeEvent &&
+          e10sRolloutProcessCountPref.value &&
           processCountPref.value == processCountPref.defaultValue) {
         processCountPref.value = e10sRolloutProcessCountPref.value;
       }
       performanceSettings.hidden = false;
     }
   },
 
   buildContentProcessCountMenuList() {
--- a/browser/components/preferences/in-content-new/privacy.xul
+++ b/browser/components/preferences/in-content-new/privacy.xul
@@ -593,30 +593,30 @@
 <groupbox id="dataCollectionGroup" data-category="panePrivacy" data-subcategory="reports" hidden="true">
   <description>
     &dataCollectionDesc.label;<label id="dataCollectionPrivacyNotice" class="learnMore text-link">&dataCollectionPrivacyNotice.label;</label>
   </description>
 
   <vbox>
     <description flex="1">
       <checkbox id="submitHealthReportBox" label="&enableHealthReport1.label;"
-                accesskey="&enableHealthReport1.accesskey;" flex="1" />
+                accesskey="&enableHealthReport1.accesskey;"/>
       <label id="FHRLearnMore"
              class="learnMore text-link">&healthReportLearnMore.label;</label>
     </description>
 #ifndef MOZ_TELEMETRY_REPORTING
     <description id="TelemetryDisabledDesc" class="indent" control="telemetryGroup">&healthReportingDisabled.label;</description>
 #endif
   </vbox>
 #ifdef MOZ_CRASHREPORTER
   <hbox align="center">
     <checkbox id="automaticallySubmitCrashesBox"
               preference="browser.crashReports.unsubmittedCheck.autoSubmit"
               label="&alwaysSubmitCrashReports1.label;"
-              accesskey="&alwaysSubmitCrashReports1.accesskey;" flex="1" />
+              accesskey="&alwaysSubmitCrashReports1.accesskey;"/>
     <label id="crashReporterLearnMore"
            class="learnMore text-link">&crashReporterLearnMore.label;</label>
   </hbox>
 #endif
 </groupbox>
 #endif
 
 <hbox id="securityCategory"
--- a/browser/components/preferences/in-content-new/search.xul
+++ b/browser/components/preferences/in-content-new/search.xul
@@ -25,19 +25,21 @@
           data-category="paneSearch">
       <label class="header-name" flex="1">&paneSearch.title;</label>
     </hbox>
 
     <!-- Default Search Engine -->
     <groupbox id="defaultEngineGroup" data-category="paneSearch">
       <caption label="&defaultSearchEngine.label;"/>
       <label>&chooseYourDefaultSearchEngine2.label;</label>
-      <menulist id="defaultEngine">
-        <menupopup/>
-      </menulist>
+      <hbox>
+        <menulist id="defaultEngine">
+          <menupopup/>
+        </menulist>
+      </hbox>
       <checkbox id="suggestionsInSearchFieldsCheckbox"
                 label="&provideSearchSuggestions.label;"
                 accesskey="&provideSearchSuggestions.accesskey;"
                 preference="browser.search.suggest.enabled"/>
       <vbox class="indent">
         <checkbox id="urlBarSuggestion" label="&showURLBarSuggestions2.label;"
                   accesskey="&showURLBarSuggestions2.accesskey;"
                   preference="browser.urlbar.suggest.searches"/>
--- a/browser/components/preferences/in-content-new/sync.js
+++ b/browser/components/preferences/in-content-new/sync.js
@@ -314,16 +314,23 @@ var gSyncPane = {
     }).then(isVerified => {
       if (isVerified) {
         return fxAccounts.getSignedInUserProfile();
       }
       return null;
     }).then(data => {
       let fxaLoginStatus = document.getElementById("fxaLoginStatus");
       if (data) {
+        if (data.email) {
+          // A hack to handle that the user's email address may have changed.
+          // This can probably be removed as part of bug 1383663.
+          fxaEmailAddress1Label.textContent = data.email;
+          document.getElementById("fxaEmailAddress2").textContent = data.email;
+          document.getElementById("fxaEmailAddress3").textContent = data.email;
+        }
         if (data.displayName) {
           fxaLoginStatus.setAttribute("hasName", true);
           displayNameLabel.hidden = false;
           displayNameLabel.textContent = data.displayName;
         } else {
           fxaLoginStatus.removeAttribute("hasName");
         }
         if (data.avatar) {
--- a/browser/components/preferences/in-content-new/tests/browser.ini
+++ b/browser/components/preferences/in-content-new/tests/browser.ini
@@ -39,31 +39,34 @@ skip-if = os != "win" || (os == "win" &&
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_layersacceleration.js]
 [browser_masterpassword.js]
 [browser_notifications_do_not_disturb.js]
 [browser_password_management.js]
 [browser_performance.js]
 skip-if = !e10s
+[browser_performance_e10srollout.js]
+skip-if = !e10s
 [browser_performance_non_e10s.js]
 skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchsuggestions.js]
 [browser_security-1.js]
 [browser_security-2.js]
 [browser_siteData.js]
 [browser_siteData2.js]
 [browser_site_login_exceptions.js]
+[browser_cookies_dialog.js]
 [browser_subdialogs.js]
 support-files =
   subdialog.xul
   subdialog2.xul
 [browser_telemetry.js]
 # Skip this test on Android as FHR and Telemetry are separate systems there.
 skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content-new/tests/browser_cookies_dialog.js
@@ -0,0 +1,50 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const COOKIES_URL = "chrome://browser/content/preferences/cookies.xul";
+
+const URI = Services.io.newURI("http://www.example.com");
+var cookiesDialog;
+
+add_task(async function openCookiesSubDialog() {
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+
+  let dialogOpened = promiseLoadSubDialog(COOKIES_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let cookiesButton = doc.getElementById("historyRememberCookies");
+    cookiesButton.click();
+  });
+
+  cookiesDialog = await dialogOpened;
+});
+
+add_task(async function testDeleteCookie() {
+  let doc = cookiesDialog.document;
+
+  // Add a cookie.
+  Services.cookies.add(URI.host, URI.path, "", "", false, false, true, Date.now());
+
+  let tree = doc.getElementById("cookiesList");
+  Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
+  tree.focus();
+  tree.view.selection.select(0);
+
+  if (AppConstants.platform == "macosx") {
+    EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+  } else {
+    EventUtils.synthesizeKey("VK_DELETE", {});
+  }
+
+  await waitForCondition(() => tree.view.rowCount == 0);
+
+  is_element_visible(content.gSubDialog._dialogs[0]._box,
+    "Subdialog is visible after deleting an element");
+
+});
+
+add_task(async function removeTab() {
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content-new/tests/browser_performance_e10srollout.js
@@ -0,0 +1,102 @@
+const DEFAULT_HW_ACCEL_PREF = Services.prefs.getDefaultBranch(null).getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs.getDefaultBranch(null).getIntPref("dom.ipc.processCount");
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+    ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+    ["browser.preferences.defaultPerformanceSettings.enabled", true],
+  ]});
+});
+
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
+    "pref value should be true before clicking on checkbox");
+  ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
+
+  useRecommendedPerformanceSettings.click();
+
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
+     "pref value should be false after clicking on checkbox");
+  ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 1, "e10s rollout value should be default value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 1, "selected item should be the default one");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
+
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default value should be the current value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
+
+add_task(async function() {
+  Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT + 2);
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 2, "process count should be the set value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 2, "selected item should be the set one");
+
+  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
+  useRecommendedPerformanceSettings.click();
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
+    "pref value should be true after clicking on checkbox");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT,
+    "process count should be default value");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -45,19 +45,19 @@ var gMainPane = {
     }
 
     this.buildContentProcessCountMenuList();
     this.updateDefaultPerformanceSettingsPref();
 
     let defaultPerformancePref =
       document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
     defaultPerformancePref.addEventListener("change", () => {
-      this.updatePerformanceSettingsBox();
+      this.updatePerformanceSettingsBox({duringChangeEvent: true});
     });
-    this.updatePerformanceSettingsBox();
+    this.updatePerformanceSettingsBox({duringChangeEvent: false});
 
     let performanceSettingsLink = document.getElementById("performanceSettingsLearnMore");
     let performanceSettingsUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "performance";
     performanceSettingsLink.setAttribute("href", performanceSettingsUrl);
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
@@ -420,33 +420,34 @@ var gMainPane = {
     let processCountPref = document.getElementById("dom.ipc.processCount");
     let accelerationPref = document.getElementById("layers.acceleration.disabled");
     if (processCountPref.value != processCountPref.defaultValue ||
         accelerationPref.value != accelerationPref.defaultValue) {
       defaultPerformancePref.value = false;
     }
   },
 
-  updatePerformanceSettingsBox() {
+  updatePerformanceSettingsBox({duringChangeEvent}) {
     let defaultPerformancePref =
       document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
     let performanceSettings = document.getElementById("performanceSettings");
     let processCountPref = document.getElementById("dom.ipc.processCount");
     if (defaultPerformancePref.value) {
       let accelerationPref = document.getElementById("layers.acceleration.disabled");
       // Unset the value so process count will be decided by e10s rollout.
       processCountPref.value = processCountPref.defaultValue;
       accelerationPref.value = accelerationPref.defaultValue;
       performanceSettings.hidden = true;
     } else {
       let e10sRolloutProcessCountPref =
         document.getElementById("dom.ipc.processCount.web");
       // Take the e10s rollout value as the default value (if it exists),
       // but don't overwrite the user set value.
-      if (e10sRolloutProcessCountPref.value &&
+      if (duringChangeEvent &&
+          e10sRolloutProcessCountPref.value &&
           processCountPref.value == processCountPref.defaultValue) {
         processCountPref.value = e10sRolloutProcessCountPref.value;
       }
       performanceSettings.hidden = false;
     }
   },
 
   buildContentProcessCountMenuList() {
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -309,16 +309,23 @@ var gSyncPane = {
     }).then(isVerified => {
       if (isVerified) {
         return fxAccounts.getSignedInUserProfile();
       }
       return null;
     }).then(data => {
       let fxaLoginStatus = document.getElementById("fxaLoginStatus");
       if (data) {
+        if (data.email) {
+          // A hack to handle that the user's email address may have changed.
+          // This can probably be removed as part of bug 1383663.
+          fxaEmailAddress1Label.textContent = data.email;
+          document.getElementById("fxaEmailAddress2").textContent = data.email;
+          document.getElementById("fxaEmailAddress3").textContent = data.email;
+        }
         if (data.displayName) {
           fxaLoginStatus.setAttribute("hasName", true);
           displayNameLabel.hidden = false;
           displayNameLabel.textContent = data.displayName;
         } else {
           fxaLoginStatus.removeAttribute("hasName");
         }
         if (data.avatar) {
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -27,30 +27,33 @@ skip-if = os != "win" # This test tests 
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_notifications_do_not_disturb.js]
 [browser_password_management.js]
 [browser_performance.js]
 skip-if = !e10s
+[browser_performance_e10srollout.js]
+skip-if = !e10s
 [browser_performance_non_e10s.js]
 skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchsuggestions.js]
 [browser_security.js]
 [browser_siteData.js]
 [browser_siteData2.js]
 [browser_site_login_exceptions.js]
+[browser_cookies_dialog.js]
 [browser_subdialogs.js]
 support-files =
   subdialog.xul
   subdialog2.xul
 [browser_telemetry.js]
 # Skip this test on Android as FHR and Telemetry are separate systems there.
 skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_cookies_dialog.js
@@ -0,0 +1,50 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const COOKIES_URL = "chrome://browser/content/preferences/cookies.xul";
+
+const URI = Services.io.newURI("http://www.example.com");
+var cookiesDialog;
+
+add_task(async function openCookiesSubDialog() {
+  await openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true});
+
+  let dialogOpened = promiseLoadSubDialog(COOKIES_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let cookiesButton = doc.getElementById("historyRememberCookies");
+    cookiesButton.click();
+  });
+
+  cookiesDialog = await dialogOpened;
+});
+
+add_task(async function testDeleteCookie() {
+  let doc = cookiesDialog.document;
+
+  // Add a cookie.
+  Services.cookies.add(URI.host, URI.path, "", "", false, false, true, Date.now());
+
+  let tree = doc.getElementById("cookiesList");
+  Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
+  tree.focus();
+  tree.view.selection.select(0);
+
+  if (AppConstants.platform == "macosx") {
+    EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+  } else {
+    EventUtils.synthesizeKey("VK_DELETE", {});
+  }
+
+  await waitForCondition(() => tree.view.rowCount == 0);
+
+  is_element_visible(content.gSubDialog._dialogs[0]._box,
+    "Subdialog is visible after deleting an element");
+
+});
+
+add_task(async function removeTab() {
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_performance_e10srollout.js
@@ -0,0 +1,102 @@
+const DEFAULT_HW_ACCEL_PREF = Services.prefs.getDefaultBranch(null).getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs.getDefaultBranch(null).getIntPref("dom.ipc.processCount");
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+    ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+    ["browser.preferences.defaultPerformanceSettings.enabled", true],
+  ]});
+});
+
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
+    "pref value should be true before clicking on checkbox");
+  ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
+
+  useRecommendedPerformanceSettings.click();
+
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
+     "pref value should be false after clicking on checkbox");
+  ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 1, "e10s rollout value should be default value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 1, "selected item should be the default one");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
+
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default value should be the current value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
+
+add_task(async function() {
+  Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT + 2);
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 2, "process count should be the set value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 2, "selected item should be the set one");
+
+  let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
+  useRecommendedPerformanceSettings.click();
+
+  is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
+    "pref value should be true after clicking on checkbox");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT,
+    "process count should be default value");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1135,26 +1135,21 @@ this.UITour = {
       // Close a previous highlight so we can relocate the panel.
       if (highlighter.parentElement.state == "showing" || highlighter.parentElement.state == "open") {
         log.debug("showHighlight: Closing previous highlight first");
         highlighter.parentElement.hidePopup();
       }
       /* The "overlap" position anchors from the top-left but we want to centre highlights at their
          minimum size. */
       let highlightWindow = aChromeWindow;
-      let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
-      let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
-      let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
       let highlightStyle = highlightWindow.getComputedStyle(highlighter);
       let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
       let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
-      let offsetX = paddingTopPx
-                      - (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
-      let offsetY = paddingLeftPx
-                      - (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
+      let offsetX = -(Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
+      let offsetY = -(Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
       this._addAnnotationPanelMutationObserver(highlighter.parentElement);
       highlighter.parentElement.openPopup(highlightAnchor, "overlap", offsetX, offsetY);
     }
 
     // Prevent showing a panel at an undefined position.
     if (!this.isElementVisible(aTarget.node)) {
       log.warn("showHighlight: Not showing a highlight since the target isn't visible", aTarget);
       return;
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -26,22 +26,21 @@ const MULTI_EXPERIMENT = {
 
             // When on the "beta" channel, getAddonsDisqualifyForMulti
             // will return true if any addon installed is not a web extension.
             // Therefore, this returns true if and only if all addons
             // installed are web extensions or if no addons are installed
             // at all.
             addonsDisableExperiment(prefix) { return getAddonsDisqualifyForMulti(); } },
 
-  "release": { buckets: { 1: .99, 4: 1 }, // 1 process: 99%, 4 processes: 1%
+  "release": { buckets: { 1: .5, 4: 1 }, // 1 process: 50%, 4 processes: 50%
 
-               // We don't want to allow users with any extension
-               // (webextension or otherwise in the experiment). prefix will
-               // be non-empty if there is any addon.
-               addonsDisableExperiment(prefix) { return !!prefix; } }
+               // See above for an explanation of this: we only allow users
+               // with no extensions or users with WebExtensions.
+               addonsDisableExperiment(prefix) { return getAddonsDisqualifyForMulti(); } }
 };
 
 const ADDON_ROLLOUT_POLICY = {
   "beta": "50allmpc",
   "release": "50allmpc",
   "esr": "esrA", // WebExtensions and Addons with mpc=true
 };
 
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.85</em:version>
+    <em:version>2.0</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -17,131 +17,302 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
 
 /**
+ * A scanner for traversing all elements in a form and retrieving the field
+ * detail with FormAutofillHeuristics.getInfo function. It also provides a
+ * cursor (parsingIndex) to indicate which element is waiting for parsing.
+ */
+class FieldScanner {
+  /**
+   * Create a FieldScanner based on form elements with the existing
+   * fieldDetails.
+   *
+   * @param {Array.DOMElement} elements
+   *        The elements from a form for each parser.
+   */
+  constructor(elements) {
+    this._elementsWeakRef = Cu.getWeakReference(elements);
+    this.fieldDetails = [];
+    this._parsingIndex = 0;
+  }
+
+  get _elements() {
+    return this._elementsWeakRef.get();
+  }
+
+  /**
+   * This cursor means the index of the element which is waiting for parsing.
+   *
+   * @returns {number}
+   *          The index of the element which is waiting for parsing.
+   */
+  get parsingIndex() {
+    return this._parsingIndex;
+  }
+
+  /**
+   * Move the parsingIndex to the next elements. Any elements behind this index
+   * means the parsing tasks are finished.
+   *
+   * @param {number} index
+   *        The latest index of elements waiting for parsing.
+   */
+  set parsingIndex(index) {
+    if (index > this.fieldDetails.length) {
+      throw new Error("The parsing index is out of range.");
+    }
+    this._parsingIndex = index;
+  }
+
+  /**
+   * Retrieve the field detail by the index. If the field detail is not ready,
+   * the elements will be traversed until matching the index.
+   *
+   * @param {number} index
+   *        The index of the element that you want to retrieve.
+   * @returns {Object}
+   *          The field detail at the specific index.
+   */
+  getFieldDetailByIndex(index) {
+    if (index >= this._elements.length) {
+      throw new Error(`The index ${index} is out of range.(${this._elements.length})`);
+    }
+
+    if (index < this.fieldDetails.length) {
+      return this.fieldDetails[index];
+    }
+
+    for (let i = this.fieldDetails.length; i < (index + 1); i++) {
+      this.pushDetail();
+    }
+
+    return this.fieldDetails[index];
+  }
+
+  get parsingFinished() {
+    return this.parsingIndex >= this._elements.length;
+  }
+
+  /**
+   * This function will prepare an autocomplete info object with getInfo
+   * function and push the detail to fieldDetails property. Any duplicated
+   * detail will be marked as _duplicated = true for the parser.
+   *
+   * Any element without the related detail will be used for adding the detail
+   * to the end of field details.
+   */
+  pushDetail() {
+    let elementIndex = this.fieldDetails.length;
+    if (elementIndex >= this._elements.length) {
+      throw new Error("Try to push the non-existing element info.");
+    }
+    let element = this._elements[elementIndex];
+    let info = FormAutofillHeuristics.getInfo(element);
+    if (!info) {
+      info = {};
+    }
+    let fieldInfo = {
+      section: info.section,
+      addressType: info.addressType,
+      contactType: info.contactType,
+      fieldName: info.fieldName,
+      elementWeakRef: Cu.getWeakReference(element),
+    };
+
+    // Store the association between the field metadata and the element.
+    if (this.findSameField(info) != -1) {
+      // A field with the same identifier already exists.
+      log.debug("Not collecting a field matching another with the same info:", info);
+      fieldInfo._duplicated = true;
+    }
+
+    this.fieldDetails.push(fieldInfo);
+  }
+
+  /**
+   * When a field detail should be changed its fieldName after parsing, use
+   * this function to update the fieldName which is at a specific index.
+   *
+   * @param {number} index
+   *        The index indicates a field detail to be updated.
+   * @param {string} fieldName
+   *        The new fieldName
+   */
+  updateFieldName(index, fieldName) {
+    if (index >= this.fieldDetails.length) {
+      throw new Error("Try to update the non-existing field detail.");
+    }
+    this.fieldDetails[index].fieldName = fieldName;
+
+    delete this.fieldDetails[index]._duplicated;
+    let indexSame = this.findSameField(this.fieldDetails[index]);
+    if (indexSame != index && indexSame != -1) {
+      this.fieldDetails[index]._duplicated = true;
+    }
+  }
+
+  findSameField(info) {
+    return this.fieldDetails.findIndex(f => f.section == info.section &&
+                                       f.addressType == info.addressType &&
+                                       f.contactType == info.contactType &&
+                                       f.fieldName == info.fieldName);
+  }
+
+  /**
+   * Provide the field details without invalid field name and duplicated fields.
+   *
+   * @returns {Array<Object>}
+   *          The array with the field details without invalid field name and
+   *          duplicated fields.
+   */
+  get trimmedFieldDetail() {
+    return this.fieldDetails.filter(f => f.fieldName && !f._duplicated);
+  }
+
+  elementExisting(index) {
+    return index < this._elements.length;
+  }
+}
+
+/**
  * Returns the autocomplete information of fields according to heuristics.
  */
 this.FormAutofillHeuristics = {
-  FIELD_GROUPS: {
-    NAME: [
-      "name",
-      "given-name",
-      "additional-name",
-      "family-name",
-    ],
-    ADDRESS: [
-      "organization",
-      "street-address",
-      "address-line1",
-      "address-line2",
-      "address-line3",
-      "address-level2",
-      "address-level1",
-      "postal-code",
-      "country",
-    ],
-    TEL: ["tel"],
-    EMAIL: ["email"],
-  },
-
   RULES: null,
 
-  getFormInfo(form) {
-    let fieldDetails = [];
-    if (form.autocomplete == "off") {
-      return [];
-    }
-    for (let element of form.elements) {
-      // Exclude elements to which no autocomplete field has been assigned.
-      let info = this.getInfo(element, fieldDetails);
-      if (!info) {
-        continue;
+  /**
+   * Try to match the telephone related fields to the grammar
+   * list to see if there is any valid telephone set and correct their
+   * field names.
+   *
+   * @param {FieldScanner} fieldScanner
+   *        The current parsing status for all elements
+   * @returns {boolean}
+   *          Return true if there is any field can be recognized in the parser,
+   *          otherwise false.
+   */
+  _parsePhoneFields(fieldScanner) {
+    let matchingResult;
+
+    const GRAMMARS = this.PHONE_FIELD_GRAMMARS;
+    for (let i = 0; i < GRAMMARS.length; i++) {
+      let detailStart = fieldScanner.parsingIndex;
+      let ruleStart = i;
+      for (; i < GRAMMARS.length && GRAMMARS[i][0] && fieldScanner.elementExisting(detailStart); i++, detailStart++) {
+        let detail = fieldScanner.getFieldDetailByIndex(detailStart);
+        if (!detail || GRAMMARS[i][0] != detail.fieldName) {
+          break;
+        }
+        let element = detail.elementWeakRef.get();
+        if (!element) {
+          break;
+        }
+        if (GRAMMARS[i][2] && (!element.maxLength || GRAMMARS[i][2] < element.maxLength)) {
+          break;
+        }
+      }
+      if (i >= GRAMMARS.length) {
+        break;
       }
 
-      // Store the association between the field metadata and the element.
-      if (fieldDetails.some(f => f.section == info.section &&
-                                 f.addressType == info.addressType &&
-                                 f.contactType == info.contactType &&
-                                 f.fieldName == info.fieldName)) {
-        // A field with the same identifier already exists.
-        log.debug("Not collecting a field matching another with the same info:", info);
-        continue;
+      if (!GRAMMARS[i][0]) {
+        matchingResult = {
+          ruleFrom: ruleStart,
+          ruleTo: i,
+        };
+        break;
       }
 
-      let formatWithElement = {
-        section: info.section,
-        addressType: info.addressType,
-        contactType: info.contactType,
-        fieldName: info.fieldName,
-        elementWeakRef: Cu.getWeakReference(element),
-      };
-
-      fieldDetails.push(formatWithElement);
+      // Fast rewinding to the next rule.
+      for (; i < GRAMMARS.length; i++) {
+        if (!GRAMMARS[i][0]) {
+          break;
+        }
+      }
     }
 
-    return fieldDetails;
+    let parsedField = false;
+    if (matchingResult) {
+      let {ruleFrom, ruleTo} = matchingResult;
+      let detailStart = fieldScanner.parsingIndex;
+      for (let i = ruleFrom; i < ruleTo; i++) {
+        fieldScanner.updateFieldName(detailStart, GRAMMARS[i][1]);
+        fieldScanner.parsingIndex++;
+        detailStart++;
+        parsedField = true;
+      }
+    }
+
+    if (fieldScanner.parsingFinished) {
+      return parsedField;
+    }
+
+    let nextField = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
+    if (nextField && nextField.fieldName == "tel-extension") {
+      fieldScanner.parsingIndex++;
+      parsedField = true;
+    }
+
+    return parsedField;
   },
 
   /**
-   * Get the autocomplete info (e.g. fieldName) determined by the regexp
-   * (this.RULES) matching to a feature string. The result is based on the
-   * existing field names to prevent duplicating predictions
-   * (e.g. address-line[1-3).
+   * Try to find the correct address-line[1-3] sequence and correct their field
+   * names.
    *
-   * @param {string} string a feature string to be determined.
-   * @param {Array<string>} existingFieldNames the array of exising field names
-   *                        in a form.
-   * @returns {Object}
-   *          Provide the predicting result including the field name.
-   *
+   * @param {FieldScanner} fieldScanner
+   *        The current parsing status for all elements
+   * @returns {boolean}
+   *          Return true if there is any field can be recognized in the parser,
+   *          otherwise false.
    */
-  _matchStringToFieldName(string, existingFieldNames) {
-    let result = {
-      fieldName: "",
-      section: "",
-      addressType: "",
-      contactType: "",
-    };
-    if (this.RULES.email.test(string)) {
-      result.fieldName = "email";
-      return result;
+  _parseAddressFields(fieldScanner) {
+    let parsedFields = false;
+    let addressLines = ["address-line1", "address-line2", "address-line3"];
+    for (let i = 0; !fieldScanner.parsingFinished && i < addressLines.length; i++) {
+      let detail = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
+      if (!detail || !addressLines.includes(detail.fieldName)) {
+        // When the field is not related to any address-line[1-3] fields, it
+        // means the parsing process can be terminated.
+        break;
+      }
+      fieldScanner.updateFieldName(fieldScanner.parsingIndex, addressLines[i]);
+      fieldScanner.parsingIndex++;
+      parsedFields = true;
     }
-    if (this.RULES.tel.test(string)) {
-      result.fieldName = "tel";
-      return result;
+
+    return parsedFields;
+  },
+
+  getFormInfo(form) {
+    if (form.autocomplete == "off" || form.elements.length <= 0) {
+      return [];
     }
-    for (let fieldName of this.FIELD_GROUPS.ADDRESS) {
-      if (this.RULES[fieldName].test(string)) {
-        // If "address-line1" or "address-line2" exist already, the string
-        // could be satisfied with "address-line2" or "address-line3".
-        if ((fieldName == "address-line1" &&
-            existingFieldNames.includes("address-line1")) ||
-            (fieldName == "address-line2" &&
-            existingFieldNames.includes("address-line2"))) {
-          continue;
-        }
-        result.fieldName = fieldName;
-        return result;
+
+    let fieldScanner = new FieldScanner(form.elements);
+    while (!fieldScanner.parsingFinished) {
+      let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
+      let parsedAddressFields = this._parseAddressFields(fieldScanner);
+
+      // If there is no any field parsed, the parsing cursor can be moved
+      // forward to the next one.
+      if (!parsedPhoneFields && !parsedAddressFields) {
+        fieldScanner.parsingIndex++;
       }
     }
-    for (let fieldName of this.FIELD_GROUPS.NAME) {
-      if (this.RULES[fieldName].test(string)) {
-        result.fieldName = fieldName;
-        return result;
-      }
-    }
-    return null;
+    return fieldScanner.trimmedFieldDetail;
   },
 
-  getInfo(element, fieldDetails) {
+  getInfo(element) {
     if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
       return null;
     }
 
     let info = element.getAutocompleteInfo();
     // An input[autocomplete="on"] will not be early return here since it stll
     // needs to find the field name.
     if (info && info.fieldName && info.fieldName != "on") {
@@ -160,43 +331,166 @@ this.FormAutofillHeuristics = {
       return {
         fieldName: "email",
         section: "",
         addressType: "",
         contactType: "",
       };
     }
 
-    let existingFieldNames = fieldDetails ? fieldDetails.map(i => i.fieldName) : "";
+    let regexps = Object.keys(this.RULES);
 
-    for (let elementString of [element.id, element.name]) {
-      let fieldNameResult = this._matchStringToFieldName(elementString,
-                                                         existingFieldNames);
-      if (fieldNameResult) {
-        return fieldNameResult;
+    let labelStrings;
+    let getElementStrings = {};
+    getElementStrings[Symbol.iterator] = function* () {
+      yield element.id;
+      yield element.name;
+      if (!labelStrings) {
+        labelStrings = [];
+        let labels = FormAutofillUtils.findLabelElements(element);
+        for (let label of labels) {
+          labelStrings.push(...FormAutofillUtils.extractLabelStrings(label));
+        }
       }
-    }
-    let labels = FormAutofillUtils.findLabelElements(element);
-    if (!labels || labels.length == 0) {
-      log.debug("No label found for", element);
-      return null;
-    }
-    for (let label of labels) {
-      let strings = FormAutofillUtils.extractLabelStrings(label);
-      for (let string of strings) {
-        let fieldNameResult = this._matchStringToFieldName(string,
-                                                           existingFieldNames);
-        if (fieldNameResult) {
-          return fieldNameResult;
+      yield *labelStrings;
+    };
+
+    for (let regexp of regexps) {
+      for (let string of getElementStrings) {
+        if (this.RULES[regexp].test(string)) {
+          return {
+            fieldName: regexp,
+            section: "",
+            addressType: "",
+            contactType: "",
+          };
         }
       }
     }
 
     return null;
   },
+
+/**
+ * Phone field grammars - first matched grammar will be parsed. Grammars are
+ * separated by { REGEX_SEPARATOR, FIELD_NONE, 0 }. Suffix and extension are
+ * parsed separately unless they are necessary parts of the match.
+ * The following notation is used to describe the patterns:
+ * <cc> - country code field.
+ * <ac> - area code field.
+ * <phone> - phone or prefix.
+ * <suffix> - suffix.
+ * <ext> - extension.
+ * :N means field is limited to N characters, otherwise it is unlimited.
+ * (pattern <field>)? means pattern is optional and matched separately.
+ *
+ * This grammar list from Chromium will be enabled partially once we need to
+ * support more cases of Telephone fields.
+ */
+  PHONE_FIELD_GRAMMARS: [
+    // Country code: <cc> Area Code: <ac> Phone: <phone> (- <suffix>
+
+    // (Ext: <ext>)?)?
+      // {REGEX_COUNTRY, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_AREA, FIELD_AREA_CODE, 0},
+      // {REGEX_PHONE, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // \( <ac> \) <phone>:3 <suffix>:4 (Ext: <ext>)?
+      // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 3},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
+      // {REGEX_PHONE, FIELD_SUFFIX, 4},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <cc> <ac>:3 - <phone>:3 - <suffix>:4 (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_PHONE, FIELD_AREA_CODE, 3},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
+      // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <cc>:3 <ac>:3 <phone>:3 <suffix>:4 (Ext: <ext>)?
+    ["tel", "tel-country-code", 3],
+    ["tel", "tel-area-code", 3],
+    ["tel", "tel-local-prefix", 3],
+    ["tel", "tel-local-suffix", 4],
+    [null, null, 0],
+
+    // Area Code: <ac> Phone: <phone> (- <suffix> (Ext: <ext>)?)?
+      // {REGEX_AREA, FIELD_AREA_CODE, 0},
+      // {REGEX_PHONE, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <ac> <phone>:3 <suffix>:4 (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_AREA_CODE, 0},
+      // {REGEX_PHONE, FIELD_PHONE, 3},
+      // {REGEX_PHONE, FIELD_SUFFIX, 4},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <cc> \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <cc> - <ac> - <phone> - <suffix> (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
+      // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Area code: <ac>:3 Prefix: <prefix>:3 Suffix: <suffix>:4 (Ext: <ext>)?
+      // {REGEX_AREA, FIELD_AREA_CODE, 3},
+      // {REGEX_PREFIX, FIELD_PHONE, 3},
+      // {REGEX_SUFFIX, FIELD_SUFFIX, 4},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <ac> Prefix: <phone> Suffix: <suffix> (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_AREA_CODE, 0},
+      // {REGEX_PREFIX, FIELD_PHONE, 0},
+      // {REGEX_SUFFIX, FIELD_SUFFIX, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <ac> - <phone>:3 - <suffix>:4 (Ext: <ext>)?
+    ["tel", "tel-area-code", 0],
+    ["tel", "tel-local-prefix", 3],
+    ["tel", "tel-local-suffix", 4],
+    [null, null, 0],
+
+    // Phone: <cc> - <ac> - <phone> (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
+      // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
+      // {REGEX_SUFFIX_SEPARATOR, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <ac> - <phone> (Ext: <ext>)?
+      // {REGEX_AREA, FIELD_AREA_CODE, 0},
+      // {REGEX_PHONE, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <cc>:3 - <phone>:10 (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_COUNTRY_CODE, 3},
+      // {REGEX_PHONE, FIELD_PHONE, 10},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Ext: <ext>
+      // {REGEX_EXTENSION, FIELD_EXTENSION, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+
+    // Phone: <phone> (Ext: <ext>)?
+      // {REGEX_PHONE, FIELD_PHONE, 0},
+      // {REGEX_SEPARATOR, FIELD_NONE, 0},
+  ],
 };
 
 XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "RULES", () => {
   let sandbox = {};
   let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                        .getService(Ci.mozIJSSubScriptLoader);
   const HEURISTICS_REGEXP = "chrome://formautofill/content/heuristicsRegexp.js";
   scriptLoader.loadSubScript(HEURISTICS_REGEXP, sandbox, "utf-8");
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -6,16 +6,17 @@
 
 this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
   _fieldNameInfo: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
@@ -32,16 +33,17 @@ this.FormAutofillUtils = {
     "country-name": "address",
     "tel": "tel",
     "tel-country-code": "tel",
     "tel-national": "tel",
     "tel-area-code": "tel",
     "tel-local": "tel",
     "tel-local-prefix": "tel",
     "tel-local-suffix": "tel",
+    "tel-extension": "tel",
     "email": "email",
     "cc-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
   },
   _addressDataLoaded: false,
 
@@ -197,41 +199,47 @@ this.FormAutofillUtils = {
   loadDataFromScript(url, sandbox = {}) {
     let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                          .getService(Ci.mozIJSSubScriptLoader);
     scriptLoader.loadSubScript(url, sandbox, "utf-8");
     return sandbox;
   },
 
   /**
+   * Get country address data. Fallback to US if not found.
+   * @param   {string} country
+   * @returns {object}
+   */
+  getCountryAddressData(country) {
+    // Load the addressData if needed
+    if (!this._addressDataLoaded) {
+      Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
+      this._addressDataLoaded = true;
+    }
+    return this.addressData[`data/${country}`] || this.addressData["data/US"];
+  },
+
+  /**
    * Find the option element from select element.
    * 1. Try to find the locale using the country from address.
    * 2. First pass try to find exact match.
    * 3. Second pass try to identify values from address value and options,
    *    and look for a match.
    * @param   {DOMElement} selectEl
    * @param   {object} address
    * @param   {string} fieldName
    * @returns {DOMElement}
    */
   findSelectOption(selectEl, address, fieldName) {
     let value = address[fieldName];
     if (!value) {
       return null;
     }
 
-    // Load the addressData if needed
-    if (!this._addressDataLoaded) {
-      Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
-      this._addressDataLoaded = true;
-    }
-
-    // Set dataset to "data/US" as fallback
-    let dataset = this.addressData[`data/${address.country}`] ||
-                  this.addressData["data/US"];
+    let dataset = this.getCountryAddressData(address.country);
     let collator = new Intl.Collator(dataset.lang, {sensitivity: "base", ignorePunctuation: true});
 
     for (let option of selectEl.options) {
       if (this.strCompare(value, option.value, collator) ||
           this.strCompare(value, option.text, collator)) {
         return option;
       }
     }
@@ -298,12 +306,43 @@ this.FormAutofillUtils = {
    * @param   {string} a
    * @param   {string} b
    * @param   {object} collator
    * @returns {boolean}
    */
   strCompare(a = "", b = "", collator) {
     return !collator.compare(a, b);
   },
+
+  /**
+   * Get formatting information of a given country
+   * @param   {string} country
+   * @returns {object}
+   *         {
+   *           {string} addressLevel1Label
+   *           {string} postalCodeLabel
+   *         }
+   */
+  getFormFormat(country) {
+    const dataset = this.getCountryAddressData(country);
+    return {
+      "addressLevel1Label": dataset.state_name_type || "province",
+      "postalCodeLabel": dataset.zip_name_type || "postalCode",
+    };
+  },
+
+  /**
+   * Localize elements with "data-localization" attribute
+   * @param   {string} bundleURI
+   * @param   {DOMElement} root
+   */
+  localizeMarkup(bundleURI, root) {
+    const bundle = Services.strings.createBundle(bundleURI);
+    let elements = root.querySelectorAll("[data-localization]");
+    for (let element of elements) {
+      element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
+      element.removeAttribute("data-localization");
+    }
+  },
 };
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1336,17 +1336,17 @@ class Addresses extends AutofillRecords 
       number = address["tel-national"];
     } else if (address["tel-local"]) {
       number = (address["tel-area-code"] || "") + address["tel-local"];
     } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
       number = (address["tel-area-code"] || "") + address["tel-local-prefix"] + address["tel-local-suffix"];
     }
 
     let tel = PhoneNumber.Parse(number, region);
-    if (tel) {
+    if (tel && tel.internationalNumber) {
       // Force to save numbers in E.164 format if parse success.
       address.tel = tel.internationalNumber;
     } else if (!address.tel) {
       // Save the original number anyway if "tel" is omitted.
       address.tel = number;
     }
 
     TEL_COMPONENTS.forEach(c => delete address[c]);
--- a/browser/extensions/formautofill/content/editAddress.js
+++ b/browser/extensions/formautofill/content/editAddress.js
@@ -1,40 +1,78 @@
 /* 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
+const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 function EditDialog() {
   this._address = window.arguments && window.arguments[0];
   window.addEventListener("DOMContentLoaded", this, {once: true});
 }
 
 EditDialog.prototype = {
-  init() {
-    this._elements = {
+  get _elements() {
+    if (this._elementRefs) {
+      return this._elementRefs;
+    }
+    this._elementRefs = {
+      title: document.querySelector("title"),
+      addressLevel1Label: document.querySelector("#address-level1-container > span"),
+      postalCodeLabel: document.querySelector("#postal-code-container > span"),
+      country: document.getElementById("country"),
       controlsContainer: document.getElementById("controls-container"),
       cancel: document.getElementById("cancel"),
       save: document.getElementById("save"),
     };
+    return this._elementRefs;
+  },
+
+  set _elements(refs) {
+    this._elementRefs = refs;
+  },
+
+  init() {
     this.attachEventListeners();
   },
 
   uninit() {
     this.detachEventListeners();
     this._elements = null;
   },
 
   /**
+   * Format the form based on country. The address-level1 and postal-code labels
+   * should be specific to the given country.
+   * @param  {string} country
+   */
+  formatForm(country) {
+    // TODO: Use fmt to show/hide and order fields (Bug 1383687)
+    const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
+    this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
+    this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  },
+
+  localizeDocument() {
+    if (this._address) {
+      this._elements.title.dataset.localization = "editDialogTitle";
+    }
+    FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
+    this.formatForm(this._address && this._address.country);
+  },
+
+  /**
    * Asks FormAutofillParent to save or update an address.
    * @param  {object} data
    *         {
    *           {string} guid [optional]
    *           {object} address
    *         }
    */
   saveAddress(data) {
@@ -72,17 +110,16 @@ EditDialog.prototype = {
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "DOMContentLoaded": {
         this.init();
         if (this._address) {
-          document.title = "Edit Address";
           this.loadInitialValues(this._address);
         }
         break;
       }
       case "click": {
         this.handleClick(event);
         break;
       }
@@ -156,9 +193,9 @@ EditDialog.prototype = {
    */
   detachEventListeners() {
     window.removeEventListener("keypress", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     document.removeEventListener("input", this);
   },
 };
 
-new EditDialog();
+window.dialog = new EditDialog();
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -1,68 +1,73 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-  <title>Add New Address</title>
+  <title data-localization="addNewDialogTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css" />
   <link rel="stylesheet" href="chrome://formautofill/skin/editAddress.css" />
   <script src="chrome://formautofill/content/editAddress.js"></script>
 </head>
 <body>
   <form autocomplete="off">
     <label id="given-name-container">
-      <span>First Name</span>
+      <span data-localization="givenName"/>
       <input id="given-name" type="text"/>
     </label>
     <label id="additional-name-container">
-      <span>Middle Name</span>
+      <span data-localization="additionalName"/>
       <input id="additional-name" type="text"/>
     </label>
     <label id="family-name-container">
-      <span>Last Name</span>
+      <span data-localization="familyName"/>
       <input id="family-name" type="text"/>
     </label>
     <label id="organization-container">
-      <span>Company</span>
+      <span data-localization="organization"/>
       <input id="organization" type="text"/>
     </label>
     <label id="street-address-container">
-      <span>Street Address</span>
+      <span data-localization="streetAddress"/>
       <textarea id="street-address" rows="3"/>
     </label>
     <label id="address-level2-container">
-      <span>City/Town</span>
+      <span data-localization="city"/>
       <input id="address-level2" type="text"/>
     </label>
     <label id="address-level1-container">
-      <span>State/Province</span>
+      <span/>
       <input id="address-level1" type="text"/>
     </label>
     <label id="postal-code-container">
-      <span>Zip/Postal</span>
+      <span/>
       <input id="postal-code" type="text"/>
     </label>
     <label id="country-container">
-      <span>Country</span>
+      <span data-localization="country"/>
       <select id="country">
         <option/>
-        <option value="US">United States</option>
+        <option value="US" data-localization="us"/>
       </select>
     </label>
     <label id="email-container">
-      <span>Email</span>
+      <span data-localization="email"/>
       <input id="email" type="email"/>
     </label>
     <label id="tel-container">
-      <span>Phone</span>
+      <span data-localization="tel"/>
       <input id="tel" type="tel"/>
     </label>
   </form>
   <div id="controls-container">
-    <button id="cancel">Cancel</button>
-    <button id="save" disabled="disabled">Save</button>
+    <button id="cancel" data-localization="cancel"/>
+    <button id="save" disabled="disabled" data-localization="save"/>
   </div>
+  <script type="application/javascript"><![CDATA[
+    "use strict";
+    // Localize strings before DOMContentLoaded to prevent flash
+    window.dialog.localizeDocument();
+  ]]></script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -174,25 +174,26 @@
            *
            * @private
            * @param {string|string[]} categories
            *        A list of categories that used to generate the message.
            * @param {boolean} hasExtraCategories
            *        Used to determine if it has the extra categories other than the focued category. If
            *        the value is true, we show "Also fill ...", otherwise, show "Fill ..." only.
            */
+          const namespace = "category.";
           this._updateText = (categories = this._allFieldCategories, hasExtraCategories = true) => {
             let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
             let sep = this._stringBundle.GetStringFromName("fieldNameSeparator");
             // Show the categories in certain order to conform with the spec.
             let orderedCategoryList = ["address", "name", "organization", "tel", "email"];
             let showCategories = hasExtraCategories ?
               orderedCategoryList.filter(category => categories.includes(category) && category != this._focusedCategory) :
               [this._focusedCategory];
-            let categoriesText = showCategories.map(this._stringBundle.GetStringFromName).join(sep);
+            let categoriesText = showCategories.map(category => this._stringBundle.GetStringFromName(namespace + category)).join(sep);
 
             this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
               [categoriesText], 1);
             this.parentNode.parentNode.adjustHeight();
           };
 
           /**
            * A handler for updating warning message once selectedIndex has been changed.
--- a/browser/extensions/formautofill/content/heuristicsRegexp.js
+++ b/browser/extensions/formautofill/content/heuristicsRegexp.js
@@ -24,16 +24,21 @@ var HeuristicsRegExp = {
       "|Электронной.?Почты" +                       // ru
       "|邮件|邮箱" +                                // zh-CN
       "|電郵地址" +                                 // zh-TW
       "|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?", // ko-KR
       "iu"
     ),
 
     // ==== Telephone ====
+    "tel-extension": new RegExp(
+      "\\bext|ext\\b|extension" +
+      "|ramal",                     // pt-BR, pt-PT
+      "iu"
+    ),
     "tel": new RegExp(
       "phone|mobile|contact.?number" +
       "|telefonnummer" +                             // de-DE
       "|telefono|teléfono" +                         // es
       "|telfixe" +                                   // fr-FR
       "|電話" +                                      // ja-JP
       "|telefone|telemovel" +                        // pt-BR, pt-PT
       "|телефон" +                                   // ru
@@ -150,16 +155,27 @@ var HeuristicsRegExp = {
       "|país|pais" + // es
       "|国" +        // ja-JP
       "|国家" +      // zh-CN
       "|국가|나라",  // ko-KR
       "iu"
     ),
 
     // ==== Name Fields ====
+    "name": new RegExp(
+      "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
+      "|name.*first.*last|firstandlastname" +
+      "|nombre.*y.*apellidos" + // es
+      "|^nom" +                 // fr-FR
+      "|お名前|氏名" +          // ja-JP
+      "|^nome" +                // pt-BR, pt-PT
+      "|姓名" +                 // zh-CN
+      "|성명",                  // ko-KR
+      "iu"
+    ),
     "given-name": new RegExp(
       "first.*name|initials|fname|first$|given.*name" +
       "|vorname" +                // de-DE
       "|nombre" +                 // es
       "|forename|prénom|prenom" + // fr-FR
       "|名" +                     // ja-JP
       "|nome" +                   // pt-BR, pt-PT
       "|Имя" +                    // ru
@@ -181,21 +197,10 @@ var HeuristicsRegExp = {
       "|famille|^nom" +                       // fr-FR
       "|cognome" +                            // it-IT
       "|姓" +                                 // ja-JP
       "|morada|apelidos|surename|sobrenome" + // pt-BR, pt-PT
       "|Фамилия" +                            // ru
       "|\\b성(?:[^명]|\\b)",                  // ko-KR
       "iu"
     ),
-    "name": new RegExp(
-      "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
-      "|name.*first.*last|firstandlastname" +
-      "|nombre.*y.*apellidos" + // es
-      "|^nom" +                 // fr-FR
-      "|お名前|氏名" +          // ja-JP
-      "|^nome" +                // pt-BR, pt-PT
-      "|姓名" +                 // zh-CN
-      "|성명",                  // ko-KR
-      "iu"
-    ),
   },
 };
--- a/browser/extensions/formautofill/content/manageAddresses.js
+++ b/browser/extensions/formautofill/content/manageAddresses.js
@@ -1,16 +1,17 @@
 /* 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
+const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
 
@@ -52,16 +53,20 @@ ManageAddressDialog.prototype = {
   },
 
   uninit() {
     log.debug("uninit");
     this.detachEventListeners();
     this._elements = null;
   },
 
+  localizeDocument() {
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  },
+
   /**
    * Load addresses and render them.
    *
    * @returns {promise}
    */
   loadAddresses() {
     return this.getRecords({collectionName: "addresses"}).then(addresses => {
       log.debug("addresses:", addresses);
@@ -297,9 +302,9 @@ ManageAddressDialog.prototype = {
     window.removeEventListener("keypress", this);
     this._elements.addresses.removeEventListener("change", this);
     this._elements.addresses.removeEventListener("click", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 };
 
-new ManageAddressDialog();
+window.dialog = new ManageAddressDialog();
--- a/browser/extensions/formautofill/content/manageAddresses.xhtml
+++ b/browser/extensions/formautofill/content/manageAddresses.xhtml
@@ -1,29 +1,34 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-  <title>Saved Addresses</title>
+  <title data-localization="manageDialogTitle"/>
   <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
   <link rel="stylesheet" href="chrome://formautofill/content/manageAddresses.css" />
   <script src="chrome://formautofill/content/manageAddresses.js"></script>
 </head>
 <body>
   <p style="padding-left: 30px; background: url(chrome://browser/skin/warning.svg) no-repeat left center">
     Autofill of addresses is only ready for testing with United States addresses on &lt;input&gt;s and some &lt;select&gt; elements.
     Improvements to form field type detection are in progress.
     <a href="https://luke-chang.github.io/autofill-demo/basic.html" target="_blank">Demo page</a>
   </p>
   <fieldset>
-    <legend>Addresses</legend>
+    <legend data-localization="addressListHeader"/>
     <select id="addresses" size="9" multiple="multiple"/>
   </fieldset>
   <div id="controls-container">
-    <button id="remove" disabled="disabled">Remove</button>
-    <button id="add">Add…</button>
-    <button id="edit" disabled="disabled">Edit…</button>
+    <button id="remove" disabled="disabled" data-localization="remove"/>
+    <button id="add" data-localization="add"/>
+    <button id="edit" disabled="disabled" data-localization="edit"/>
   </div>
+  <script type="application/javascript">
+    "use strict";
+    // Localize strings before DOMContentLoaded to prevent flash
+    window.dialog.localizeDocument();
+  </script>
 </body>
 </html>
rename from browser/extensions/formautofill/locale/en-US/formautofill.properties
rename to browser/extensions/formautofill/locales/en-US/formautofill.properties
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -13,21 +13,45 @@ changeAutofillOptionsOSX = Change Form A
 updateAddressMessage = Would you like to update your address with this new information?
 createAddressLabel = Create New Address
 updateAddressLabel = Update Address
 openAutofillMessagePanel = Open Form Autofill message panel
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionShort = More Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 autocompleteFooterOptionOSXShort = Preferences
-address = address
-name = name
-organization = company
-tel = phone
-email = email
+category.address = address
+category.name = name
+category.organization = company
+category.tel = phone
+category.email = email
 # LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
 fieldNameSeparator = ,\u0020
 # LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
 # text that is displayed for informing users what categories are about to be filled.
 # "%S" will be replaced with a list generated from the pre-defined categories.
 # The text would be e.g. Also fill company, phone, email
 phishingWarningMessage = Also autofills %S
 phishingWarningMessage2 = Autofills %S
+
+manageDialogTitle = Saved Addresses
+addressListHeader = Addresses
+remove = Remove
+add = Add…
+edit = Edit…
+
+addNewDialogTitle = Add New Address
+editDialogTitle = Edit Address
+givenName = First Name
+additionalName = Middle Name
+familyName = Last Name
+organization = Company
+streetAddress = Street Address
+city = City
+province = Province
+state = State
+postalCode = Postal Code
+zip = Zip Code
+country = Country or Region
+tel = Phone
+email = Email
+cancel = Cancel
+save = Save
rename from browser/extensions/formautofill/locale/jar.mn
rename to browser/extensions/formautofill/locales/jar.mn
rename from browser/extensions/formautofill/locale/moz.build
rename to browser/extensions/formautofill/locales/moz.build
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -2,17 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
 
-DIRS += ['locale']
+DIRS += ['locales']
 
 FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
   'bootstrap.js'
 ]
 
 FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
   'install.rdf.in'
 ]
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -1,12 +1,16 @@
 /* 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/. */
 
+html {
+  width: 620px;
+}
+
 body {
   font-size: 1rem;
 }
 
 form,
 label,
 div {
   display: flex;
@@ -17,28 +21,28 @@ form {
 }
 
 label {
   margin: 0 0 0.5em;
 }
 
 label > span {
   box-sizing: border-box;
-  flex: 0 0 8em;
+  flex: 0 0 9.5em;
   padding-inline-end: 0.5em;
   align-self: center;
   text-align: end;
   -moz-user-select: none;
 }
 
 input,
 select {
   box-sizing: border-box;
   flex: 1 0 auto;
-  width: calc(50% - 8em);
+  width: calc(50% - 9.5em);
 }
 
 option {
   padding: 5px 10px;
 }
 
 textarea {
   resize: none;
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
@@ -10,33 +10,38 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"}, // FIXME: ZIP ext
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+        // FIXME: The below "tel-extension" is correct and removed due to the
+        // duplicated field above.
+//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
       ],
       [],
     ],
   }, {
     fixturePath: "Checkout_BillingPaymentInfo.html",
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"}, // FIXME: ZIP ext
       ],
       [
  /* TODO: Credit Card
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_OfficeDepot.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_OfficeDepot.js
@@ -11,22 +11,20 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
 
-        // TODO: telphone relative fields should be fixed:
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
       [],
     ],
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
@@ -37,22 +35,20 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
 
-        // TODO: telphone relative fields should be fixed:
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
       [ // ac-off
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Sears.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Sears.js
@@ -63,17 +63,17 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
       ],
       [ // check out
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: Wrong. This is for Driver's license.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
@@ -28,16 +28,20 @@ runHeuristicsTest([
       [
       ],
       [
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+        // FIXME The following field shouldn't be recognized as "tel-extension".
+        // The wrong prediction is caused by the name attr "brwsrAutofillText"
+        // which matches the regexp "ext\\b".
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "tel"},
       ],
     ],
   }, {
     fixturePath: "Shipping.html",
     expectedResult: [
       [
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -6,32 +6,32 @@
 
 Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><select id="country"></select>
-               <input id='email'><input id="tel"></form>`,
+               <input id='email'><input id="phone"></form>`,
     addressFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [],
     isValidForm: {
       address: true,
       creditCard: false,
     },
-    ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "tel"],
+    ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "phone"],
   },
   {
     description: "An address and credit card form with autocomplete properties and 1 token",
     document: `<form>
                <input id="given-name" autocomplete="given-name">
                <input id="family-name" autocomplete="family-name">
                <input id="street-addr" autocomplete="street-address">
                <input id="city" autocomplete="address-level2">
@@ -150,16 +150,117 @@ const TESTCASES = [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
     ],
     isValidForm: {
       address: false,
       creditCard: false,
     },
   },
+  {
+    description: "Three sets of adjacent phone number fields",
+    document: `<form>
+                 <input id="shippingAreaCode" autocomplete="shipping tel" maxlength="3">
+                 <input id="shippingPrefix" autocomplete="shipping tel" maxlength="3">
+                 <input id="shippingSuffix" autocomplete="shipping tel" maxlength="4">
+                 <input id="shippingTelExt" autocomplete="shipping tel-extension">
+
+                 <input id="billingAreaCode" autocomplete="billing tel" maxlength="3">
+                 <input id="billingPrefix" autocomplete="billing tel" maxlength="3">
+                 <input id="billingSuffix" autocomplete="billing tel" maxlength="4">
+
+                 <input id="otherCountryCode" autocomplete="tel" maxlength="3">
+                 <input id="otherAreaCode" autocomplete="tel" maxlength="3">
+                 <input id="otherPrefix" autocomplete="tel" maxlength="3">
+                 <input id="otherSuffix" autocomplete="tel" maxlength="4">
+               </form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-extension"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+    ],
+    creditCardFieldDetails: [],
+    isValidForm: {
+      address: true,
+      creditCard: false,
+    },
+    ids: [
+      "shippingAreaCode", "shippingPrefix", "shippingSuffix", "shippingTelExt",
+      "billingAreaCode", "billingPrefix", "billingSuffix",
+      "otherCountryCode", "otherAreaCode", "otherPrefix", "otherSuffix",
+    ],
+  },
+  {
+    description: "Dedup the same field names of the different telephone fields.",
+    document: `<form>
+                 <input id="i1" autocomplete="shipping given-name">
+                 <input id="i2" autocomplete="shipping family-name">
+                 <input id="i3" autocomplete="shipping street-address">
+                 <input id="i4" autocomplete="shipping email">
+
+                 <input id="homePhone" maxlength="10">
+                 <input id="mobilePhone" maxlength="10">
+                 <input id="officePhone" maxlength="10">
+               </form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+    ],
+    creditCardFieldDetails: [],
+    isValidForm: {
+      address: true,
+      creditCard: false,
+    },
+    ids: ["i1", "i2", "i3", "i4", "homePhone"],
+  },
+  {
+    description: "The duplicated phones of a single one and a set with ac, prefix, suffix.",
+    document: `<form>
+                 <input id="i1" autocomplete="shipping given-name">
+                 <input id="i2" autocomplete="shipping family-name">
+                 <input id="i3" autocomplete="shipping street-address">
+                 <input id="i4" autocomplete="shipping email">
+                 <input id="singlePhone" autocomplete="shipping tel">
+                 <input id="shippingAreaCode" autocomplete="shipping tel-area-code">
+                 <input id="shippingPrefix" autocomplete="shipping tel-local-prefix">
+                 <input id="shippingSuffix" autocomplete="shipping tel-local-suffix">
+               </form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+
+      // NOTES: Ideally, there is only one full telephone field(s) in a form for
+      // this case. We can see if there is any better solution later.
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
+    ],
+    creditCardFieldDetails: [],
+    isValidForm: {
+      address: true,
+      creditCard: false,
+    },
+    ids: ["i1", "i2", "i3", "i4", "singlePhone",
+      "shippingAreaCode", "shippingPrefix", "shippingSuffix"],
+  },
 ];
 
 for (let tc of TESTCASES) {
   (function() {
     let testcase = tc;
     add_task(async function() {
       do_print("Starting testcase: " + testcase.description);
 
@@ -180,22 +281,23 @@ for (let tc of TESTCASES) {
         }
         detail.elementWeakRef = Cu.getWeakReference(elementRef);
       });
       let handler = new FormAutofillHandler(formLike);
 
       handler.collectFormFields();
 
       function verifyDetails(handlerDetails, testCaseDetails) {
+        Assert.equal(handlerDetails.length, testCaseDetails.length);
         handlerDetails.forEach((detail, index) => {
-          Assert.equal(detail.section, testCaseDetails[index].section);
-          Assert.equal(detail.addressType, testCaseDetails[index].addressType);
-          Assert.equal(detail.contactType, testCaseDetails[index].contactType);
-          Assert.equal(detail.fieldName, testCaseDetails[index].fieldName);
-          Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get());
+          Assert.equal(detail.fieldName, testCaseDetails[index].fieldName, "fieldName");
+          Assert.equal(detail.section, testCaseDetails[index].section, "section");
+          Assert.equal(detail.addressType, testCaseDetails[index].addressType, "addressType");
+          Assert.equal(detail.contactType, testCaseDetails[index].contactType, "contactType");
+          Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get(), "DOM reference");
         });
       }
 
       verifyDetails(handler.addressFieldDetails, testcase.addressFieldDetails);
       verifyDetails(handler.creditCardFieldDetails, testcase.creditCardFieldDetails);
 
       Assert.equal(handler.isValidAddressForm, testcase.isValidForm.address, "Valid Address Form Checking");
       Assert.equal(handler.isValidCreditCardForm, testcase.isValidForm.creditCard, "Valid Credit Card Form Checking");
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -88,18 +88,18 @@ const TESTCASES = [
                <input id="line1" autocomplete="address-line1">
                <input id="line3" autocomplete="address-line3">
                </form>`,
     profileData: [Object.assign({}, DEFAULT_PROFILE)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
-      "address-line1": "2 Harrison St line2",
-      "address-line2": "line2",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2 line3",
       "address-line3": "line3",
     }],
   },
 ];
 
 for (let testcase of TESTCASES) {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
--- a/browser/extensions/formautofill/test/unit/test_getInfo.js
+++ b/browser/extensions/formautofill/test/unit/test_getInfo.js
@@ -91,39 +91,22 @@ const TESTCASES = [
     expectedReturnValue: {
       fieldName: "address-level1",
       section: "",
       addressType: "",
       contactType: "",
     },
   },
   {
-    description: "2 address line inputs",
+    description: "address line input",
     document: `<label for="targetElement">street</label>
                <input id="targetElement" type="text">`,
     elementId: "targetElement",
-    addressFieldDetails: [{fieldName: "address-line1"}],
     expectedReturnValue: {
-      fieldName: "address-line2",
-      section: "",
-      addressType: "",
-      contactType: "",
-    },
-  },
-  {
-    description: "3 address line inputs",
-    document: `<label for="targetElement">street</label>
-               <input id="targetElement" type="text">`,
-    elementId: "targetElement",
-    addressFieldDetails: [
-      {fieldName: "address-line1"},
-      {fieldName: "address-line2"},
-    ],
-    expectedReturnValue: {
-      fieldName: "address-line3",
+      fieldName: "address-line1",
       section: "",
       addressType: "",
       contactType: "",
     },
   },
   {
     description: "CJK character - Traditional Chinese",
     document: `<label> 郵遞區號
@@ -213,13 +196,13 @@ const TESTCASES = [
 TESTCASES.forEach(testcase => {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
     let doc = MockDocument.createTestDocument(
       "http://localhost:8080/test/", testcase.document);
 
     let element = doc.getElementById(testcase.elementId);
-    let value = FormAutofillHeuristics.getInfo(element, testcase.addressFieldDetails);
+    let value = FormAutofillHeuristics.getInfo(element);
 
     Assert.deepEqual(value, testcase.expectedReturnValue);
   });
 });
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -386,16 +386,25 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     address: {
       "tel": "12345",
     },
     expectedResult: {
       "tel": "12345",
     },
   },
   {
+    description: "Has a valid tel-local format \"tel\"",
+    address: {
+      "tel": "1234567",
+    },
+    expectedResult: {
+      "tel": "1234567",
+    },
+  },
+  {
     description: "Has \"tel-national\" and \"tel-country-code\"",
     address: {
       "tel-national": "0212345678",
       "tel-country-code": "+886",
     },
     expectedResult: {
       "tel": "+886212345678",
     },
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -43,26 +43,26 @@ const PROMPT_COUNT_PREF = "browser.onboa
  **/
 var onboardingTourset = {
   "private": {
     id: "onboarding-tour-private-browsing",
     tourNameId: "onboarding.tour-private-browsing",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"),
-        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message"),
+        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message2"),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
     },
     getPage(win) {
       let div = win.document.createElement("div");
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-private-browsing.title2"></h1>
-          <p data-l10n-id="onboarding.tour-private-browsing.description2"></p>
+          <p data-l10n-id="onboarding.tour-private-browsing.description3"></p>
         </section>
         <section class="onboarding-tour-content">
           <img src="resource://onboarding/img/figure_private.svg" role="presentation"/>
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-private-browsing-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
         </aside>
       `;
@@ -678,27 +678,26 @@ class Onboarding {
       return;
     }
     let targetTourId = queue[0];
     let targetTour = this._tours.find(tour => tour.id == targetTourId);
 
     // Show the target tour notification
     this._notificationBar = this._renderNotificationBar();
     this._notificationBar.addEventListener("click", this);
-    this._window.document.body.appendChild(this._notificationBar);
-
     this._notificationBar.dataset.targetTourId = targetTour.id;
     let notificationStrings = targetTour.getNotificationStrings(this._bundle);
     let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn");
     actionBtn.textContent = notificationStrings.button;
     let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title");
     tourTitle.textContent = notificationStrings.title;
     let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
     tourMessage.textContent = notificationStrings.message;
     this._notificationBar.classList.add("onboarding-opened");
+    this._window.document.body.appendChild(this._notificationBar);
 
     let params = [];
     if (startQueueLength != queue.length) {
       // We just change tour so update the time, the count and the queue
       params.push({
         name: "browser.onboarding.notification.last-time-of-changing-tour-sec",
         value: Math.floor(Date.now() / 1000)
       });
@@ -743,16 +742,20 @@ class Onboarding {
       </section>
       <button id="onboarding-notification-close-btn" class="onboarding-close-btn"></button>
     `;
     let toolTip = this._bundle.formatStringFromName(
       this._tourType === "new" ? "onboarding.notification-icon-tool-tip" :
                                  "onboarding.notification-icon-tooltip-updated",
       [BRAND_SHORT_NAME], 1);
     div.querySelector("#onboarding-notification-icon").setAttribute("data-tooltip", toolTip);
+
+    let closeBtn = div.querySelector("#onboarding-notification-close-btn");
+    closeBtn.setAttribute("title",
+      this._bundle.GetStringFromName("onboarding.notification-close-button-tooltip"));
     return div;
   }
 
   hide() {
     this.setToursCompleted(this._tours.map(tour => tour.id));
     this.sendMessageToChrome("set-prefs", [
       {
         name: "browser.onboarding.hidden",
@@ -779,19 +782,22 @@ class Onboarding {
         </nav>
         <footer id="onboarding-footer">
           <input type="checkbox" id="onboarding-tour-hidden-checkbox" /><label for="onboarding-tour-hidden-checkbox"></label>
         </footer>
       </div>
     `;
 
     div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
-       this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
+      this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
     div.querySelector("#onboarding-header").textContent =
-       this._bundle.GetStringFromName("onboarding.overlay-title2");
+      this._bundle.GetStringFromName("onboarding.overlay-title2");
+    let closeBtn = div.querySelector("#onboarding-overlay-close-btn");
+    closeBtn.setAttribute("title",
+      this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
     return div;
   }
 
   _renderOverlayButton() {
     let button = this._window.document.createElement("button");
     let tooltipStringId = this._tourType === "new" ?
       "onboarding.overlay-icon-tooltip" : "onboarding.overlay-icon-tooltip-updated";
     let tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
@@ -877,14 +883,20 @@ if (Services.prefs.getBoolPref("browser.
     if (!content || evt.target != content.document) {
       return;
     }
     removeEventListener("load", onLoad);
 
     let window = evt.target.defaultView;
     let location = window.location.href;
     if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
+      // We just want to run tests as quick as possible
+      // so in the automation test, we don't do `requestIdleCallback`.
+      if (Cu.isInAutomation) {
+        new Onboarding(window);
+        return;
+      }
       window.requestIdleCallback(() => {
         new Onboarding(window);
       });
     }
   }, true);
 }
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -7,37 +7,41 @@ onboarding.hidden-checkbox-label-text=Ma
 #LOCALIZATION NOTE(onboarding.button.learnMore): this string is used as a button label, displayed near the message, and shared across all the onboarding notifications.
 onboarding.button.learnMore=Learn More
 # LOCALIZATION NOTE(onboarding.notification-icon-tool-tip): This string will be used to show the tooltip alongside the notification icon in the notification bar. %S is brandShortName.
 onboarding.notification-icon-tool-tip=New to %S?
 # LOCALIZATION NOTE(onboarding.overlay-icon-tooltip): This string will be used to show the tooltip alongside the notification icon in the overlay tour. %S is brandShortName.
 onboarding.overlay-icon-tooltip=New to %S? Let’s get started.
 # LOCALIZATION NOTE(onboarding.overlay-icon-tooltip-updated): %S is brandShortName.
 onboarding.overlay-icon-tooltip-updated=%S is all new. See what you can do!
+# LOCALIZATION NOTE(onboarding.overlay-close-button-tooltip): The overlay close button is an icon button. This tooltip would be shown when mousing hovering on the button.
+onboarding.overlay-close-button-tooltip=Close
 onboarding.notification-icon-tooltip-updated=See what’s new!
+# LOCALIZATION NOTE(onboarding.notification-close-button-tooltip): The notification close button is an icon button. This tooltip would be shown when mousing hovering on the button.
+onboarding.notification-close-button-tooltip=Dismiss
 
 onboarding.tour-search2=Search
 onboarding.tour-search.title2=Find it faster.
 # LOCALIZATION NOTE (onboarding.tour-search.description2): If Amazon is not part
 # of the default searchplugins for your locale, you can replace it with another
 # ecommerce website (if you're shipping one), but not with a general purpose
 # search engine (Google, Bing, Yandex, etc.). Alternatively, only reference
 # Wikipedia and drop Amazon from the text.
 onboarding.tour-search.description2=Having a default search engine doesn’t mean it’s the only one you can use. Choose a search engine or a site, like Amazon or Wikipedia, right from the search box.
 onboarding.tour-search.button=Open One-Click Search
 onboarding.notification.onboarding-tour-search.title=Find it faster.
 onboarding.notification.onboarding-tour-search.message=Access all of your favorite search engines with a click. Search the whole Web or just one website right from the search box.
 
 onboarding.tour-private-browsing=Private Browsing
 onboarding.tour-private-browsing.title2=Browse by yourself.
-# LOCALIZATION NOTE(onboarding.tour-private-browsing.description2): %S is brandShortName.
-onboarding.tour-private-browsing.description2=Want to keep something to yourself? Use Private Browsing with Tracking Protection. When you close your session, %S clears search and browsing history.
+# LOCALIZATION NOTE(onboarding.tour-private-browsing.description3): This string will be used in the private-browsing tour description. %S is brandShortName.
+onboarding.tour-private-browsing.description3=Want to keep something to yourself? Use Private Browsing with Tracking Protection. %S will block online trackers while you browse and won’t remember your history after you’ve ended your session.
 onboarding.tour-private-browsing.button=Show Private Browsing in Menu
 onboarding.notification.onboarding-tour-private-browsing.title=Browse by yourself.
-onboarding.notification.onboarding-tour-private-browsing.message=There’s no reason to share your online life with trackers every time you browse. Want to keep something to yourself? Use Private Browsing with Tracking Protection.
+onboarding.notification.onboarding-tour-private-browsing.message2=Want to keep something to yourself? Use Private Browsing with Tracking Protection.
 
 onboarding.tour-addons=Add-ons
 onboarding.tour-addons.title2=Get more done.
 # LOCALIZATION NOTE(onboarding.tour-addons.description2): This string will be used in the add-on tour description. %S is brandShortName
 onboarding.tour-addons.description2=Add-ons let you add features to %S, so your browser works harder for you. Compare prices, check the weather or express your personality with a custom theme.
 onboarding.tour-addons.button=Show Add-ons in Menu
 onboarding.notification.onboarding-tour-addons.title=Get more done.
 # LOCALIZATION NOTE(onboarding.notification.onboarding-tour-addons.message): This string will be used in the notification message for the add-ons tour. %S is brandShortName.
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
@@ -1,64 +1,58 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(3);
+
 add_task(async function test_show_tour_notifications_in_order() {
   resetOnboardingDefaultState();
   Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
-  let reloadPromise = null;
   let expectedPrefUpdate = null;
   await loopTourNotificationQueueOnceInOrder();
   await loopTourNotificationQueueOnceInOrder();
 
   expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await expectedPrefUpdate;
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt each tour for more than 2 chances.");
   await BrowserTestUtils.removeTab(tab);
 
   async function loopTourNotificationQueueOnceInOrder() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
-        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-        tab.linkedBrowser.reload();
-        await reloadPromise;
+        await reloadTab(tab);
       } else {
-        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+        tab = await openTab(ABOUT_NEWTAB_URL);
       }
       await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
       await promiseTourNotificationOpened(tab.linkedBrowser);
       targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
       is(targetTourId, tourIds[i], "Should show tour notifications in order");
     }
   }
 });
 
 add_task(async function test_open_target_tour_from_notification() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
   let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser);
 
   is(targetTourId, activeNavItemId, "Should navigate to the target tour item.");
   is(`${targetTourId}-page`, activePageId, "Should display the target tour page.");
   await BrowserTestUtils.removeTab(tab);
 });
-
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js
@@ -1,28 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(3);
+
 add_task(async function test_not_show_notification_for_completed_tour() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   // Make only the last tour uncompleted
   let lastTourId = tourIds[tourIds.length - 1];
   for (let id of tourIds) {
     if (id != lastTourId) {
       setTourCompletedState(id, true);
     }
   }
 
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, lastTourId, "Should not show notification for completed tour");
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_skip_notification_for_completed_tour() {
@@ -30,58 +31,50 @@ add_task(async function test_skip_notifi
   Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   // Make only 2nd tour completed
   await setTourCompletedState(tourIds[1], true);
 
   // Test show notification for the 1st tour
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, tourIds[0], "Should show notification for incompleted tour");
 
   // Test skip the 2nd tour and show notification for the 3rd tour
-  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, tourIds[2], "Should skip notification for the completed 2nd tour");
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_mute_notification_on_1st_session() {
   resetOnboardingDefaultState();
 
   // Test no notifications during the mute duration on the 1st session
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   // The tour notification would be prompted on idle, so we wait idle twice here before proceeding
   await waitUntilWindowIdle(tab.linkedBrowser);
   await waitUntilWindowIdle(tab.linkedBrowser);
-  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await waitUntilWindowIdle(tab.linkedBrowser);
   await waitUntilWindowIdle(tab.linkedBrowser);
   let promptCount = Preferences.get("browser.onboarding.notification.prompt-count", 0);
   is(0, promptCount, "Should not prompt tour notification during the mute duration on the 1st session");
 
   // Test notification prompted after the mute duration on the 1st session
   let muteTime = Preferences.get("browser.onboarding.notification.mute-duration-on-first-session-ms");
   let lastTime = Math.floor((Date.now() - muteTime - 1) / 1000);
   Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime);
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   promptCount = Preferences.get("browser.onboarding.notification.prompt-count", 0);
   is(1, promptCount, "Should prompt tour notification after the mute duration on the 1st session");
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js
@@ -1,94 +1,82 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(3);
+
 add_task(async function test_move_on_to_next_notification_when_reaching_max_prompt_count() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
   let maxCount = Preferences.get("browser.onboarding.notification.max-prompt-count-per-tour");
 
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
 
   let currentTourId = null;
-  let reloadPromise = null;
   for (let i = maxCount - 1; i > 0; --i) {
-    reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-    tab.linkedBrowser.reload();
-    await reloadPromise;
+    await reloadTab(tab);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await promiseTourNotificationOpened(tab.linkedBrowser);
     currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
     is(previousTourId, currentTourId, "Should not move on to next tour notification until reaching the max prompt count per tour");
   }
 
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max prompt count per tour");
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_move_on_to_next_notification_when_reaching_max_life_time() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
 
   let maxTime = Preferences.get("browser.onboarding.notification.max-life-time-per-tour-ms");
   let lastTime = Math.floor((Date.now() - maxTime - 1) / 1000);
   Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", lastTime);
-  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   isnot(previousTourId, currentTourId, "Should move on to next tour notification when reaching the max life time per tour");
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_move_on_to_next_notification_after_interacting_with_notification() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+  let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let previousTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser);
 
-  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-close-btn");
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
   previousTourId = currentTourId;
 
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   currentTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   isnot(previousTourId, currentTourId, "Should move on to next tour notification after clicking #onboarding-notification-action-btn");
 
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
@@ -1,42 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(3);
+
 add_task(async function test_remove_all_tour_notifications_through_close_button() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
-  let reloadPromise = null;
   await closeTourNotificationsOneByOne();
 
   let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await expectedPrefUpdate;
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations.");
   await BrowserTestUtils.removeTab(tab);
 
   async function closeTourNotificationsOneByOne() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
-        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-        tab.linkedBrowser.reload();
-        await reloadPromise;
+        await reloadTab(tab);
       } else {
-        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+        tab = await openTab(ABOUT_NEWTAB_URL);
       }
       await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
       await promiseTourNotificationOpened(tab.linkedBrowser);
       targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
       is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`);
       await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-close-btn", {}, tab.linkedBrowser);
       await promiseTourNotificationClosed(tab.linkedBrowser);
     }
@@ -45,38 +41,32 @@ add_task(async function test_remove_all_
 
 add_task(async function test_remove_all_tour_notifications_through_action_button() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
-  let reloadPromise = null;
   await clickTourNotificationActionButtonsOneByOne();
 
   let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
-  reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-  tab.linkedBrowser.reload();
-  await reloadPromise;
+  await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await expectedPrefUpdate;
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations.");
   await BrowserTestUtils.removeTab(tab);
 
   async function clickTourNotificationActionButtonsOneByOne() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
-        reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
-        tab.linkedBrowser.reload();
-        await reloadPromise;
+        await reloadTab(tab);
       } else {
-        tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-        await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
+        tab = await openTab(ABOUT_NEWTAB_URL);
       }
       await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
       await promiseTourNotificationOpened(tab.linkedBrowser);
       targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
       is(targetTourId, tourIds[i], `Should show tour notifications of ${targetTourId}`);
       await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
       await promiseTourNotificationClosed(tab.linkedBrowser);
     }
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -1,13 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
  "use strict";
 
+requestLongerTimeout(2);
+
 function assertOnboardingDestroyed(browser) {
   return ContentTask.spawn(browser, {}, function() {
     let expectedRemovals = [
       "#onboarding-overlay",
       "#onboarding-overlay-button"
     ];
     for (let selector of expectedRemovals) {
       let removal = content.document.querySelector(selector);
@@ -28,18 +30,17 @@ function assertTourCompletedStyle(tourId
 }
 
 add_task(async function test_hide_onboarding_tours() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   let tabs = [];
   for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    let tab = await openTab(url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
   let expectedPrefUpdates = [
     promisePrefUpdated("browser.onboarding.hidden", true),
@@ -59,18 +60,17 @@ add_task(async function test_hide_onboar
 });
 
 add_task(async function test_click_action_button_to_set_tour_completed() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   let tabs = [];
   for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    let tab = await openTab(url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
   let completedTourId = tourIds[0];
   let expectedPrefUpdate = promisePrefUpdated(`browser.onboarding.tour.${completedTourId}.completed`, true);
@@ -81,30 +81,28 @@ add_task(async function test_click_actio
     let tab = tabs[i];
     for (let id of tourIds) {
       await assertTourCompletedStyle(id, id == completedTourId, tab.linkedBrowser);
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
 
-
 add_task(async function test_set_right_tour_completed_style_on_overlay() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   // Make the tours of even number as completed
   for (let i = 0; i < tourIds.length; ++i) {
     setTourCompletedState(tourIds[i], i % 2 == 0);
   }
 
   let tabs = [];
   for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    let tab = await openTab(url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
   for (let i = tabs.length - 1; i >= 0; --i) {
     let tab = tabs[i];
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js
@@ -1,36 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(2);
+
 add_task(async function test_onboarding_default_new_tourset() {
   resetOnboardingDefaultState();
-  let tabs = [];
-  for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
-    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
-    tabs.push(tab);
-  }
+
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
   let doc = content && content.document;
   let doms = doc.querySelectorAll(".onboarding-tour-item");
   is(doms.length, TOUR_IDs.length, "has exact tour numbers");
   doms.forEach((dom, idx) => {
     is(TOUR_IDs[idx], dom.id, "contain defined onboarding id");
   });
 
-  for (let i = tabs.length - 1; i >= 0; --i) {
-    let tab = tabs[i];
-    await BrowserTestUtils.removeTab(tab);
-  }
+  await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_onboarding_custom_new_tourset() {
   const CUSTOM_NEW_TOURs = [
     "onboarding-tour-private-browsing",
     "onboarding-tour-addons",
     "onboarding-tour-customize",
   ];
@@ -38,67 +33,51 @@ add_task(async function test_onboarding_
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [
     ["browser.onboarding.tour-type", "new"],
     ["browser.onboarding.tourset-version", 1],
     ["browser.onboarding.seen-tourset-version", 1],
     ["browser.onboarding.newtour", "private,addons,customize"],
   ]});
 
-  let tabs = [];
-  for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
-    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
-    tabs.push(tab);
-  }
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
   let doc = content && content.document;
   let doms = doc.querySelectorAll(".onboarding-tour-item");
   is(doms.length, CUSTOM_NEW_TOURs.length, "has exact tour numbers");
   doms.forEach((dom, idx) => {
     is(CUSTOM_NEW_TOURs[idx], dom.id, "contain defined onboarding id");
   });
 
-  for (let i = tabs.length - 1; i >= 0; --i) {
-    let tab = tabs[i];
-    await BrowserTestUtils.removeTab(tab);
-  }
+  await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_onboarding_custom_update_tourset() {
   const CUSTOM_UPDATE_TOURs = [
     "onboarding-tour-customize",
     "onboarding-tour-private-browsing",
     "onboarding-tour-addons",
   ];
   resetOnboardingDefaultState();
   await SpecialPowers.pushPrefEnv({set: [
     ["browser.onboarding.tour-type", "update"],
     ["browser.onboarding.tourset-version", 1],
     ["browser.onboarding.seen-tourset-version", 1],
     ["browser.onboarding.updatetour", "customize,private,addons"],
   ]});
 
-  let tabs = [];
-  for (let url of URLs) {
-    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
-    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
-    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
-    tabs.push(tab);
-  }
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
   let doc = content && content.document;
   let doms = doc.querySelectorAll(".onboarding-tour-item");
   is(doms.length, CUSTOM_UPDATE_TOURs.length, "has exact tour numbers");
   doms.forEach((dom, idx) => {
     is(CUSTOM_UPDATE_TOURs[idx], dom.id, "contain defined onboarding id");
   });
 
-  for (let i = tabs.length - 1; i >= 0; --i) {
-    let tab = tabs[i];
-    await BrowserTestUtils.removeTab(tab);
-  }
+  await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -32,37 +32,53 @@ function resetOnboardingDefaultState() {
   Preferences.reset("browser.onboarding.notification.tour-ids-queue");
   TOUR_IDs.forEach(id => Preferences.reset(`browser.onboarding.tour.${id}.completed`));
 }
 
 function setTourCompletedState(tourId, state) {
   Preferences.set(`browser.onboarding.tour.${tourId}.completed`, state);
 }
 
+async function openTab(url) {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  let loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+  await loadedPromise;
+  return tab;
+}
+
+function reloadTab(tab) {
+  let reloadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  return reloadPromise;
+}
+
 function promiseOnboardingOverlayLoaded(browser) {
-  // The onboarding overlay is init inside window.requestIdleCallback, not immediately,
-  // so we use check conditions here.
-  let condition = () => {
-    return ContentTask.spawn(browser, {}, function() {
-      return new Promise(resolve => {
-        let doc = content && content.document;
-        if (doc && doc.querySelector("#onboarding-overlay")) {
-          resolve(true);
-          return;
-        }
-        resolve(false);
+  function isLoaded() {
+    let doc = content && content.document;
+    if (doc.querySelector("#onboarding-overlay")) {
+      ok(true, "Should load onboarding overlay");
+      return Promise.resolve();
+    }
+    return new Promise(resolve => {
+      let observer = new content.MutationObserver(mutations => {
+        mutations.forEach(mutation => {
+          let overlay = Array.from(mutation.addedNodes)
+                             .find(node => node.id == "onboarding-overlay");
+          if (overlay) {
+            observer.disconnect();
+            ok(true, "Should load onboarding overlay");
+            resolve();
+          }
+        });
       });
-    })
-  };
-  return BrowserTestUtils.waitForCondition(
-    condition,
-    "Should load onboarding overlay",
-    100,
-    50 // Bug 1381335 increased retries, so debug builds can trigger idle in time
-  );
+      observer.observe(doc.body, { childList: true });
+    });
+  }
+  return ContentTask.spawn(browser, {}, isLoaded);
 }
 
 function promiseOnboardingOverlayOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let overlay = content.document.querySelector("#onboarding-overlay");
         if (overlay.classList.contains("onboarding-opened")) {
@@ -88,34 +104,39 @@ function promisePrefUpdated(name, expect
       is(expectedValue, actualValue, `Should update the pref of ${name}`);
       resolve();
     };
     Preferences.observe(name, onUpdate);
   });
 }
 
 function promiseTourNotificationOpened(browser) {
-  let condition = () => {
-    return ContentTask.spawn(browser, {}, function() {
-      return new Promise(resolve => {
-        let bar = content.document.querySelector("#onboarding-notification-bar");
-        if (bar && bar.classList.contains("onboarding-opened")) {
-          resolve(true);
-          return;
-        }
-        resolve(false);
+  function isOpened() {
+    let doc = content && content.document;
+    let notification = doc.querySelector("#onboarding-notification-bar");
+    if (notification && notification.classList.contains("onboarding-opened")) {
+      ok(true, "Should open tour notification");
+      return Promise.resolve();
+    }
+    return new Promise(resolve => {
+      let observer = new content.MutationObserver(mutations => {
+        mutations.forEach(mutation => {
+          let bar = Array.from(mutation.addedNodes)
+                         .find(node => node.id == "onboarding-notification-bar");
+          if (bar && bar.classList.contains("onboarding-opened")) {
+            observer.disconnect();
+            ok(true, "Should open tour notification");
+            resolve();
+          }
+        });
       });
-    })
-  };
-  return BrowserTestUtils.waitForCondition(
-    condition,
-    "Should open tour notification",
-    100,
-    30
-  );
+      observer.observe(doc.body, { childList: true });
+    });
+  }
+  return ContentTask.spawn(browser, {}, isOpened);
 }
 
 function promiseTourNotificationClosed(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let bar = content.document.querySelector("#onboarding-notification-bar");
         if (bar && !bar.classList.contains("onboarding-opened")) {
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -94,17 +94,17 @@ searchplugins:: $(list-json)
 DEFINES += -DBOOKMARKS_INCLUDE_DIR=$(dir $(call MERGE_FILE,profile/bookmarks.inc))
 
 libs-%:
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 ifndef RELEASE_OR_BETA
-	@$(MAKE) -C ../extensions/formautofill/locale AB_CD=$* XPI_NAME=locale-$*
+	@$(MAKE) -C ../extensions/formautofill/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../extensions/onboarding/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
 ifndef RELEASE_OR_BETA
 	@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
 endif
 ifneq '$(or $(MOZ_DEV_EDITION),$(NIGHTLY_BUILD))' ''
 	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
@@ -115,17 +115,17 @@ endif
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 
 chrome-%:
 	@$(MAKE) -C ../../toolkit/locales chrome-$*
 	@$(MAKE) -C ../../services/sync/locales chrome AB_CD=$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales chrome AB_CD=$*
 ifndef RELEASE_OR_BETA
-	@$(MAKE) -C ../extensions/formautofill/locale chrome AB_CD=$*
+	@$(MAKE) -C ../extensions/formautofill/locales chrome AB_CD=$*
 endif
 	@$(MAKE) -C ../extensions/pocket/locale chrome AB_CD=$*
 ifndef RELEASE_OR_BETA
 	@$(MAKE) -C ../extensions/presentation/locale chrome AB_CD=$*
 endif
 	@$(MAKE) -C ../../intl/locales chrome AB_CD=$*
 	@$(MAKE) -C ../../devtools/client/locales chrome AB_CD=$*
 	@$(MAKE) chrome AB_CD=$*
--- a/browser/locales/en-US/chrome/browser/pageInfo.dtd
+++ b/browser/locales/en-US/chrome/browser/pageInfo.dtd
@@ -68,16 +68,17 @@
 <!ENTITY  securityView.accesskey "V">
 <!ENTITY  securityView.unknown   "Unknown">
 
 
 <!ENTITY  securityView.identity.header   "Website Identity">
 <!ENTITY  securityView.identity.owner    "Owner:">
 <!ENTITY  securityView.identity.domain   "Website:">
 <!ENTITY  securityView.identity.verifier "Verified by:">
+<!ENTITY  securityView.identity.validity "Expires on:">
 
 <!ENTITY  securityView.privacy.header                   "Privacy &amp; History">
 <!ENTITY  securityView.privacy.history                  "Have I visited this website prior to today?">
 <!ENTITY  securityView.privacy.cookies                  "Is this website storing information (cookies) on my computer?">
 <!ENTITY  securityView.privacy.viewCookies              "View Cookies">
 <!ENTITY  securityView.privacy.viewCookies.accessKey    "k">
 <!ENTITY  securityView.privacy.passwords                "Have I saved any passwords for this website?">
 <!ENTITY  securityView.privacy.viewPasswords            "View Saved Passwords">
--- a/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
@@ -9,16 +9,18 @@
   shown. -->
 <!ENTITY safeb.palm.notdeceptive.label "This isn’t a deceptive site…">
 <!-- Localization note (safeb.palm.notdeceptive.accesskey) - Because
   safeb.palm.notdeceptive.label and reportDeceptiveSiteMenu.title from
   report-phishing.dtd are never shown at the same time, the same accesskey can
   be used for them. -->
 <!ENTITY safeb.palm.notdeceptive.accesskey "d">
 <!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
+<!-- Localization note (safeb.palm.advisory.desc) - Please don't translate <a id="advisory_provider"/> tag.  It will be replaced at runtime with advisory link-->
+<!ENTITY safeb.palm.advisory.desc "Advisory provided by <a id='advisory_provider'/>">
 
 <!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
 <!-- Localization note (safeb.blocked.malwarePage.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
 
 <!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
 <!-- Localization note (safeb.blocked.unwantedPage.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -9,16 +9,18 @@
 #UITourHighlightContainer {
   -moz-appearance: none;
   -moz-window-shadow: none;
   border: none;
   background-color: transparent;
   /* This is a buffer to compensate for the movement in the "wobble" effect,
      and for the box-shadow of #UITourHighlight. */
   padding: 4px;
+  /* Compensate the displacement caused by padding. */
+  margin: -4px;
 }
 
 #UITourHighlight {
   background-image: radial-gradient(50% 100%, rgba(0,149,220,0.4) 50%, rgba(0,149,220,0.6) 100%);
   border-radius: 40px;
   border: 1px solid white;
   /* The box-shadow opacity needs to be < 0.5 so it doesn't appear at 100% opacity
      on Linux without an X compositor where opacity is either 0 or 1. */
--- a/devtools/client/inspector/webpack.config.js
+++ b/devtools/client/inspector/webpack.config.js
@@ -100,17 +100,17 @@ module.exports = envConfig => {
           path.join(__dirname, "./webpack/devtools-utils-sham.js"),
         "devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
         "devtools/shared/platform": path.join(__dirname, "../../shared/platform/content"),
         "devtools": path.join(__dirname, "../../"),
         "gcli": path.join(__dirname, "../../shared/gcli/source/lib/gcli"),
         "method": path.join(__dirname, "../../../addon-sdk/source/lib/method"),
         "modules/libpref/init/all":
           path.join(__dirname, "../../../modules/libpref/init/all.js"),
-        "sdk/util/uuid":
+        "devtools/shared/generate-uuid":
           path.join(__dirname, "./webpack/uuid-sham.js"),
         "sdk": path.join(__dirname, "../../../addon-sdk/source/lib/sdk"),
         "Services": path.join(__dirname, "../shared/shim/Services.js"),
         "toolkit/locales":
           path.join(__dirname, "../../../toolkit/locales/en-US/chrome/global"),
       },
     },
 
--- a/devtools/client/inspector/webpack/uuid-sham.js
+++ b/devtools/client/inspector/webpack/uuid-sham.js
@@ -5,15 +5,15 @@
 "use strict";
 
 const s4 = function () {
   return Math.floor((1 + Math.random()) * 0x10000)
              .toString(16)
              .substring(1);
 };
 
-let uuid = function () {
+let generateUUID = function () {
   return "ss-s-s-s-sss".replace(/s/g, function () {
     return s4();
   });
 };
 
-module.exports = { uuid };
+module.exports = { generateUUID };
--- a/devtools/client/shared/redux/middleware/promise.js
+++ b/devtools/client/shared/redux/middleware/promise.js
@@ -1,28 +1,28 @@
 /* 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/. */
 "use strict";
 
-const uuidgen = require("sdk/util/uuid").uuid;
+const { generateUUID } = require("devtools/shared/generate-uuid");
 const defer = require("devtools/shared/defer");
 const {
   entries, toObject, executeSoon
 } = require("devtools/shared/DevToolsUtils");
 const PROMISE = exports.PROMISE = "@@dispatch/promise";
 
 function promiseMiddleware({ dispatch, getState }) {
   return next => action => {
     if (!(PROMISE in action)) {
       return next(action);
     }
 
     const promiseInst = action[PROMISE];
-    const seqId = uuidgen().toString();
+    const seqId = generateUUID().toString();
 
     // Create a new action that doesn't have the promise field and has
     // the `seqId` field that represents the sequence id
     action = Object.assign(
       toObject(entries(action).filter(pair => pair[0] !== PROMISE)), { seqId }
     );
 
     dispatch(Object.assign({}, action, { status: "start" }));
new file mode 100644
--- /dev/null
+++ b/devtools/shared/generate-uuid.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { generateUUID } =
+  Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+/**
+ * Returns a new `uuid`.
+ *
+ */
+
+module.exports = { generateUUID };
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -50,16 +50,17 @@ DevToolsModules(
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
     'event-emitter.js',
     'extend.js',
     'flags.js',
+    'generate-uuid.js',
     'indentation.js',
     'l10n.js',
     'loader-plugin-raw.jsm',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
     'plural-form.js',
     'protocol.js',
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -192,38 +192,43 @@ IMEStateManager::Shutdown()
   delete sTextCompositions;
   sTextCompositions = nullptr;
 }
 
 // static
 void
 IMEStateManager::OnTabParentDestroying(TabParent* aTabParent)
 {
+  if (sFocusedIMETabParent == aTabParent) {
+    NotifyIMEOfBlurForChildProcess();
+  }
+
   if (sActiveTabParent != aTabParent) {
     return;
   }
+
   MOZ_LOG(sISMLog, LogLevel::Info,
     ("OnTabParentDestroying(aTabParent=0x%p), "
      "The active TabParent is being destroyed", aTabParent));
 
   // The active remote process might have crashed.
   sActiveTabParent = nullptr;
 
-  // TODO: Need to cancel composition without TextComposition and make
-  //       disable IME.
+  // XXX: Need to disable IME?
 }
 
 // static
 void
 IMEStateManager::WidgetDestroyed(nsIWidget* aWidget)
 {
   if (sWidget == aWidget) {
     sWidget = nullptr;
   }
   if (sFocusedIMEWidget == aWidget) {
+    NotifyIMEOfBlurForChildProcess();
     sFocusedIMEWidget = nullptr;
   }
   if (sActiveInputContextWidget == aWidget) {
     sActiveInputContextWidget = nullptr;
   }
 }
 
 // static
@@ -243,16 +248,37 @@ IMEStateManager::StopIMEStateManagement(
   sPresContext = nullptr;
   sContent = nullptr;
   sActiveTabParent = nullptr;
   DestroyIMEContentObserver();
 }
 
 // static
 void
+IMEStateManager::NotifyIMEOfBlurForChildProcess()
+{
+  MOZ_LOG(sISMLog, LogLevel::Debug,
+    ("NotifyIMEOfBlurForChildProcess(), sFocusedIMETabParent=0x%p, "
+     "sFocusedIMEWidget=0x%p",
+     sFocusedIMETabParent.get(), sFocusedIMEWidget));
+
+  if (!sFocusedIMETabParent) {
+    MOZ_ASSERT(!sFocusedIMEWidget);
+    return;
+  }
+
+  MOZ_ASSERT(sFocusedIMEWidget);
+  NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
+
+  MOZ_ASSERT(!sFocusedIMETabParent);
+  MOZ_ASSERT(!sFocusedIMEWidget);
+}
+
+// static
+void
 IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
                                                 uint32_t aStartOffset)
 {
   if (NS_WARN_IF(!sTextCompositions)) {
     MOZ_LOG(sISMLog, LogLevel::Warning,
       ("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
        "called when there is no composition", aWidget, aStartOffset));
     return;
@@ -483,23 +509,29 @@ IMEStateManager::OnChangeFocusInternal(n
   } else {
     // If there is no active IMEContentObserver, it means that focused content
     // may be in another process.
 
     // If focus is moving from current focused remote process to different
     // process while the process has IME focus too, we need to notify IME of
     // blur here because it may be too late the blur notification to reach
     // this process especially when closing active window.
-    if (sFocusedIMETabParent &&
+    // However, don't send blur if we're being deactivated and IME wants to
+    // keep composition during deactive because notifying blur will commit
+    // or cancel composition.
+    if (sFocusedIMETabParent && sFocusedIMEWidget &&
+        (aPresContext ||
+         !sFocusedIMEWidget->IMENotificationRequestsRef().
+           WantDuringDeactive()) &&
         !IsSameProcess(sFocusedIMETabParent, newTabParent)) {
       MOZ_LOG(sISMLog, LogLevel::Info,
         ("  OnChangeFocusInternal(), notifying IME of blur of previous focused "
          "remote process because it may be too late actual notification to "
          "reach this process"));
-      NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMETabParent);
+      NotifyIMEOfBlurForChildProcess();
     }
   }
 
   if (!aPresContext) {
     MOZ_LOG(sISMLog, LogLevel::Debug,
       ("  OnChangeFocusInternal(), "
        "no nsPresContext is being activated"));
     return NS_OK;
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -269,16 +269,22 @@ protected:
                               const InputContextAction& aAction);
   static IMEState GetNewIMEState(nsPresContext* aPresContext,
                                  nsIContent* aContent);
 
   static void EnsureTextCompositionArray();
   static void CreateIMEContentObserver(EditorBase* aEditorBase);
   static void DestroyIMEContentObserver();
 
+  /**
+   * NotifyIMEOfBlurForChildProcess() tries to send blur notification when
+   * a remote process has IME focus.  Otherwise, do nothing.
+   */
+  static void NotifyIMEOfBlurForChildProcess();
+
   static bool IsEditable(nsINode* node);
 
   static bool IsIMEObserverNeeded(const IMEState& aState);
 
   static nsIContent* GetRootContent(nsPresContext* aPresContext);
 
   /**
    * CanHandleWith() returns false if aPresContext is nullptr or it's destroyed.
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: b83c200c657f6b6fb17d09f329ba77803420b46a
+Latest Commit: 0748e02d1be5f889fc17de2eb81c0c363ee3aa80
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -62,19 +62,21 @@ namespace TexSlot {
 }
 
 CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget)
   : Compositor(aWidget, aParent)
   , mAttachments(nullptr)
   , mHwnd(nullptr)
   , mDisableSequenceForNextFrame(false)
   , mAllowPartialPresents(false)
+  , mIsDoubleBuffered(false)
   , mVerifyBuffersFailed(false)
-  , mIsDoubleBuffered(false)
+  , mUseMutexOnPresent(false)
 {
+  mUseMutexOnPresent = gfxPrefs::UseMutexOnPresent();
 }
 
 CompositorD3D11::~CompositorD3D11()
 {
 }
 
 template<typename VertexType>
 void
@@ -1170,16 +1172,22 @@ CompositorD3D11::Present()
   // This must be called before present so our back buffer has the validated window content.
   if (mTarget) {
     PaintToTarget();
   }
 
   RefPtr<IDXGISwapChain1> chain;
   HRESULT hr = mSwapChain->QueryInterface((IDXGISwapChain1**)getter_AddRefs(chain));
 
+  RefPtr<IDXGIKeyedMutex> mutex;
+  if (mUseMutexOnPresent && mAttachments->mSyncTexture) {
+    mAttachments->mSyncTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
+    MOZ_ASSERT(mutex);
+  }
+
   if (SUCCEEDED(hr) && mAllowPartialPresents) {
     DXGI_PRESENT_PARAMETERS params;
     PodZero(&params);
     params.DirtyRectsCount = mBackBufferInvalid.GetNumRects();
     StackArray<RECT, 4> rects(params.DirtyRectsCount);
 
     uint32_t i = 0;
     for (auto iter = mBackBufferInvalid.RectIter(); !iter.Done(); iter.Next()) {
@@ -1187,19 +1195,39 @@ CompositorD3D11::Present()
       rects[i].left = r.x;
       rects[i].top = r.y;
       rects[i].bottom = r.YMost();
       rects[i].right = r.XMost();
       i++;
     }
 
     params.pDirtyRects = params.DirtyRectsCount ? rects.data() : nullptr;
+
+    if (mutex) {
+      hr = mutex->AcquireSync(0, 2000);
+      NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+    }
+
     chain->Present1(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, &params);
+
+    if (mutex) {
+      mutex->ReleaseSync(0);
+    }
   } else {
-    HRESULT hr = mSwapChain->Present(0, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
+    if (mutex) {
+      hr = mutex->AcquireSync(0, 2000);
+      NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+    }
+
+    hr = mSwapChain->Present(0, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
+
+    if (mutex) {
+      mutex->ReleaseSync(0);
+    }
+
     if (FAILED(hr)) {
       gfxCriticalNote << "D3D11 swap chain preset failed " << hexa(hr);
       HandleError(hr);
     }
   }
 
   if (mIsDoubleBuffered) {
     mBackBufferInvalid = mFrontBufferInvalid;
--- a/gfx/layers/d3d11/CompositorD3D11.h
+++ b/gfx/layers/d3d11/CompositorD3D11.h
@@ -231,14 +231,15 @@ private:
   bool mIsDoubleBuffered;
 
   gfx::IntRegion mFrontBufferInvalid;
   gfx::IntRegion mBackBufferInvalid;
   // This is the clip rect applied to the default DrawTarget (i.e. the window)
   gfx::IntRect mCurrentClip;
 
   bool mVerifyBuffersFailed;
+  bool mUseMutexOnPresent;
 };
 
 }
 }
 
 #endif
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -82,17 +82,17 @@ public:
 private:
   void DeleteOldAsyncImages(wr::WebRenderAPI* aApi);
 
   uint32_t GetNextResourceId() { return ++mResourceId; }
   uint32_t GetNamespace() { return mIdNamespace; }
   wr::ImageKey GenerateImageKey()
   {
     wr::ImageKey key;
-    key.mNamespace = GetNamespace();
+    key.mNamespace.mHandle = GetNamespace();
     key.mHandle = GetNextResourceId();
     return key;
   }
   bool GenerateImageKeyForTextureHost(wr::WebRenderAPI* aApi, TextureHost* aTexture, nsTArray<wr::ImageKey>& aKeys);
 
   struct ForwardingTextureHost {
     ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
       : mEpoch(aEpoch)
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -207,17 +207,17 @@ void
 WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
                                  gfx::ScaledFont* aFont, const StackingContextHelper& aSc,
                                  const LayerRect& aBounds, const LayerRect& aClip)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
   wr::WrFontKey key = GetFontKeyForScaledFont(aFont);
-  MOZ_ASSERT(key.mNamespace && key.mHandle);
+  MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
 
   for (size_t i = 0; i < aGlyphs.Length(); i++) {
     GlyphArray glyph_array = aGlyphs[i];
     nsTArray<gfx::Glyph>& glyphs = glyph_array.glyphs();
 
     nsTArray<wr::GlyphInstance> wr_glyph_instances;
     wr_glyph_instances.SetLength(glyphs.Length());
 
@@ -244,28 +244,28 @@ WebRenderBridgeChild::GetFontKeyForScale
   MOZ_ASSERT(aScaledFont);
   MOZ_ASSERT((aScaledFont->GetType() == gfx::FontType::DWRITE) ||
              (aScaledFont->GetType() == gfx::FontType::MAC) ||
              (aScaledFont->GetType() == gfx::FontType::FONTCONFIG));
 
   RefPtr<gfx::UnscaledFont> unscaled = aScaledFont->GetUnscaledFont();
   MOZ_ASSERT(unscaled);
 
-  wr::FontKey key = {0, 0};
+  wr::FontKey key = { wr::IdNamespace { 0 }, 0};
   if (mFontKeys.Get(unscaled, &key)) {
     return key;
   }
 
   FontFileData data;
   if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
       !data.mFontBuffer.mData) {
     return key;
   }
 
-  key.mNamespace = GetNamespace();
+  key.mNamespace.mHandle = GetNamespace();
   key.mHandle = GetNextResourceId();
 
   SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
 
   mFontKeys.Put(unscaled, key);
 
   return key;
 }
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -93,17 +93,17 @@ public:
   uint32_t GetNamespace() { return mIdNamespace; }
   void SetNamespace(uint32_t aIdNamespace)
   {
     mIdNamespace = aIdNamespace;
   }
 
   wr::WrImageKey GetNextImageKey()
   {
-    return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
+    return wr::WrImageKey{ wr::WrIdNamespace { GetNamespace() }, GetNextResourceId() };
   }
 
   void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
                   gfx::ScaledFont* aFont, const StackingContextHelper& aSc,
                   const LayerRect& aBounds, const LayerRect& aClip);
 
   wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
 
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -228,17 +228,17 @@ WebRenderBridgeParent::RecvAddImage(cons
                                     const gfx::SurfaceFormat& aFormat,
                                     const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace != mIdNameSpace) {
+  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(aImageKey)) == mActiveImageKeys.end());
 
   wr::ImageDescriptor descriptor(aSize, aStride, aFormat);
   mActiveImageKeys.insert(wr::AsUint64(aImageKey));
@@ -255,17 +255,17 @@ WebRenderBridgeParent::RecvAddBlobImage(
                                         const gfx::SurfaceFormat& aFormat,
                                         const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace != mIdNameSpace) {
+  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(aImageKey)) == mActiveImageKeys.end());
 
   wr::ImageDescriptor descriptor(aSize, aStride, aFormat);
   mActiveImageKeys.insert(wr::AsUint64(aImageKey));
@@ -280,17 +280,17 @@ WebRenderBridgeParent::RecvAddRawFont(co
                                       const ByteBuffer& aBuffer,
                                       const uint32_t& aFontIndex)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
   // Check if key is obsoleted.
-  if (aFontKey.mNamespace != mIdNameSpace) {
+  if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
   MOZ_ASSERT(mFontKeys.find(wr::AsUint64(aFontKey)) == mFontKeys.end());
 
   auto slice = aBuffer.AsSlice();
   mFontKeys.insert(wr::AsUint64(aFontKey));
@@ -303,17 +303,17 @@ mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aFontKey.mNamespace != mIdNameSpace) {
+  if (aFontKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   if (mFontKeys.find(wr::AsUint64(aFontKey)) != mFontKeys.end()) {
     mFontKeys.erase(wr::AsUint64(aFontKey));
     mApi->DeleteFont(aFontKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid FontKey");
@@ -329,17 +329,17 @@ WebRenderBridgeParent::RecvUpdateImage(c
                                        const ByteBuffer& aBuffer)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace != mIdNameSpace) {
+  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   wr::ImageDescriptor descriptor(aSize, aFormat);
   mApi->UpdateImageBuffer(aImageKey, descriptor, aBuffer.AsSlice());
 
   return IPC_OK();
 }
@@ -348,17 +348,17 @@ mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDeleteImage(const wr::ImageKey& aImageKey)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mApi);
 
   // Check if key is obsoleted.
-  if (aImageKey.mNamespace != mIdNameSpace) {
+  if (aImageKey.mNamespace.mHandle != mIdNameSpace) {
     return IPC_OK();
   }
 
   if (mActiveImageKeys.find(wr::AsUint64(aImageKey)) != mActiveImageKeys.end()) {
     mActiveImageKeys.erase(wr::AsUint64(aImageKey));
     mKeysToDelete.push_back(aImageKey);
   } else {
     MOZ_ASSERT_UNREACHABLE("invalid ImageKey");
@@ -566,17 +566,17 @@ WebRenderBridgeParent::ProcessWebRenderP
 {
   for (InfallibleTArray<WebRenderParentCommand>::index_type i = 0; i < aCommands.Length(); ++i) {
     const WebRenderParentCommand& cmd = aCommands[i];
     switch (cmd.type()) {
       case WebRenderParentCommand::TOpAddExternalImage: {
         const OpAddExternalImage& op = cmd.get_OpAddExternalImage();
         Range<const wr::ImageKey> keys(&op.key(), 1);
         // Check if key is obsoleted.
-        if (keys[0].mNamespace != mIdNameSpace) {
+        if (keys[0].mNamespace.mHandle != mIdNameSpace) {
           break;
         }
         MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
         MOZ_ASSERT(mActiveImageKeys.find(wr::AsUint64(keys[0])) == mActiveImageKeys.end());
         mActiveImageKeys.insert(wr::AsUint64(keys[0]));
 
         RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
         if (!host) {
@@ -1109,22 +1109,19 @@ WebRenderBridgeParent::CompositeToTarget
 
   bool scheduleComposite = false;
   nsTArray<wr::WrOpacityProperty> opacityArray;
   nsTArray<wr::WrTransformProperty> transformArray;
 
   mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
   mAsyncImageManager->ApplyAsyncImages(mApi);
 
-  if (gfxPrefs::WebRenderOMTAEnabled()) {
-    SampleAnimations(opacityArray, transformArray);
-
-    if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
-      scheduleComposite = true;
-    }
+  SampleAnimations(opacityArray, transformArray);
+  if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
+    scheduleComposite = true;
   }
 
   if (PushAPZStateToWR(transformArray)) {
     scheduleComposite = true;
   }
 
   wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId());
 
--- a/gfx/layers/wr/WebRenderContainerLayer.cpp
+++ b/gfx/layers/wr/WebRenderContainerLayer.cpp
@@ -38,18 +38,17 @@ WebRenderContainerLayer::RenderLayer(wr:
   nsTArray<LayerPolygon> children = SortChildrenBy3DZOrder(SortMode::WITHOUT_GEOMETRY);
 
   gfx::Matrix4x4 transform = GetTransform();
   gfx::Matrix4x4* transformForSC = &transform;
   float opacity = GetLocalOpacity();
   float* opacityForSC = &opacity;
   uint64_t animationsId = 0;
 
-  if (gfxPrefs::WebRenderOMTAEnabled() &&
-      !GetAnimations().IsEmpty()) {
+  if (!GetAnimations().IsEmpty()) {
     MOZ_ASSERT(GetCompositorAnimationsId());
 
     OptionalOpacity opacityForCompositor = void_t();
     OptionalTransform transformForCompositor = void_t();
 
     // Update opacity as nullptr in stacking context if there exists
     // opacity animation, the opacity value will be resolved
     // after animation sampling on the compositor
--- a/gfx/layers/wr/WebRenderContainerLayer.h
+++ b/gfx/layers/wr/WebRenderContainerLayer.h
@@ -22,19 +22,17 @@ public:
     : ContainerLayer(aManager, static_cast<WebRenderLayer*>(this))
   {
     MOZ_COUNT_CTOR(WebRenderContainerLayer);
   }
 
 protected:
   virtual ~WebRenderContainerLayer()
   {
-
-    if (gfxPrefs::WebRenderOMTAEnabled() &&
-        !GetAnimations().IsEmpty()) {
+    if (!GetAnimations().IsEmpty()) {
       mManager->AsWebRenderLayerManager()->
         AddCompositorAnimationsIdForDiscard(GetCompositorAnimationsId());
     }
 
     ContainerLayer::RemoveAllChildren();
     MOZ_COUNT_DTOR(WebRenderContainerLayer);
   }
 
--- a/gfx/layers/wr/WebRenderLayer.cpp
+++ b/gfx/layers/wr/WebRenderLayer.cpp
@@ -30,17 +30,17 @@ WebRenderLayer::WrBridge()
 {
   return WrManager()->WrBridge();
 }
 
 wr::WrImageKey
 WebRenderLayer::GenerateImageKey()
 {
   wr::WrImageKey key;
-  key.mNamespace = WrBridge()->GetNamespace();
+  key.mNamespace.mHandle = WrBridge()->GetNamespace();
   key.mHandle = WrBridge()->GetNextResourceId();
   return key;
 }
 
 Maybe<wr::WrImageMask>
 WebRenderLayer::BuildWrMaskLayer(const StackingContextHelper& aRelativeTo)
 {
   if (GetLayer()->GetMaskLayer()) {
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -31,16 +31,32 @@ struct ParamTraits<mozilla::wr::ByteBuff
     size_t length;
     return ReadParam(aMsg, aIter, &length)
         && aResult->Allocate(length)
         && aMsg->ReadBytesInto(aIter, aResult->mData, length);
   }
 };
 
 template<>
+struct ParamTraits<mozilla::wr::IdNamespace>
+{
+  static void
+  Write(Message* aMsg, const mozilla::wr::IdNamespace& aParam)
+  {
+    WriteParam(aMsg, aParam.mHandle);
+  }
+
+  static bool
+  Read(const Message* aMsg, PickleIterator* aIter, mozilla::wr::IdNamespace* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->mHandle);
+  }
+};
+
+template<>
 struct ParamTraits<mozilla::wr::ImageKey>
 {
   static void
   Write(Message* aMsg, const mozilla::wr::ImageKey& aParam)
   {
     WriteParam(aMsg, aParam.mNamespace);
     WriteParam(aMsg, aParam.mHandle);
   }
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -307,17 +307,16 @@ private:
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
   DECL_GFX_PREF(Live, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold",      APZFlingMinVelocityThreshold, float, 0.5f);
   DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold",       APZFlingStopOnTapThreshold, float, 0.05f);
   DECL_GFX_PREF(Live, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Live, "apz.frame_delay.enabled",               APZFrameDelayEnabled, bool, false);
-  DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas",    APZHighlightCheckerboardedAreas, bool, false);
   DECL_GFX_PREF(Once, "apz.keyboard.enabled",                  APZKeyboardEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.minimap.enabled",                   APZMinimap, bool, false);
   DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled",        APZMinimapVisibilityEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.one_touch_pinch.enabled",           APZOneTouchPinchEnabled, bool, true);
   DECL_GFX_PREF(Live, "apz.overscroll.enabled",                APZOverscrollEnabled, bool, false);
@@ -464,32 +463,34 @@ private:
   DECL_GFX_PREF(Live, "gfx.testing.device-reset",              DeviceResetForTesting, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.testing.device-fail",               DeviceFailForTesting, bool, false);
   DECL_GFX_PREF(Once, "gfx.text.disable-aa",                   DisableAllTextAA, bool, false);
   DECL_GFX_PREF(Live, "gfx.ycbcr.accurate-conversion",         YCbCrAccurateConversion, bool, false);
 
   // Disable surface sharing due to issues with compatible FBConfigs on
   // NVIDIA drivers as described in bug 1193015.
   DECL_GFX_PREF(Live, "gfx.use-glx-texture-from-pixmap",       UseGLXTextureFromPixmap, bool, false);
-
   DECL_GFX_PREF(Once, "gfx.use-iosurface-textures",            UseIOSurfaceTextures, bool, false);
-
+  DECL_GFX_PREF(Once, "gfx.use-mutex-on-present",              UseMutexOnPresent, bool, false);
   // These times should be in milliseconds
   DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold",    TouchResampleVsyncDelayThreshold, int32_t, 20);
   DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict",        TouchResampleMaxPredict, int32_t, 8);
   DECL_GFX_PREF(Once, "gfx.touch.resample.min-delta",          TouchResampleMinDelta, int32_t, 2);
   DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
 
   DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
-  DECL_OVERRIDE_PREF(Live, "gfx.webrender.omta.enabled",       WebRenderOMTAEnabled, gfxPrefs::OverrideBase_WebRender());
+
+  DECL_GFX_PREF(Live, "gfx.webrender.blob-images",             WebRenderBlobImages, bool, false);
+  DECL_GFX_PREF(Live, "gfx.webrender.highlight-painted-layers",WebRenderHighlightPaintedLayers, bool, false);
+  DECL_GFX_PREF(Live, "gfx.webrender.layers-free",             WebRenderLayersFree, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrender.profiler.enabled",        WebRenderProfilerEnabled, bool, false);
-  DECL_GFX_PREF(Live, "gfx.webrender.layers-free",             WebRenderLayersFree, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrendest.enabled",                WebRendestEnabled, bool, false);
+
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
   DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled",          ScreenMirroringEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist",       IgnoreDXInterop2Blacklist, bool, false);
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 #if defined(XP_MACOSX)
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
@@ -727,18 +728,16 @@ private:
   DECL_GFX_PREF(Live, "webgl.allow-immediate-queries",         WebGLImmediateQueries, bool, false);
   DECL_GFX_PREF(Live, "webgl.allow-fb-invalidation",           WebGLFBInvalidation, bool, false);
 
   DECL_GFX_PREF(Live, "webgl.perf.max-warnings",                    WebGLMaxPerfWarnings, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.perf.max-acceptable-fb-status-invals", WebGLMaxAcceptableFBStatusInvals, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.perf.spew-frame-allocs",          WebGLSpewFrameAllocs, bool, true);
 
   DECL_GFX_PREF(Live, "webgl.webgl2-compat-mode",              WebGL2CompatMode, bool, false);
-  DECL_GFX_PREF(Live, "webrender.blob-images",                 WebRenderBlobImages, bool, false);
-  DECL_GFX_PREF(Live, "webrender.highlight-painted-layers",    WebRenderHighlightPaintedLayers, bool, false);
 
   DECL_GFX_PREF(Live, "widget.window-transforms.disabled",     WindowTransformsDisabled, bool, false);
 
   // WARNING:
   // Please make sure that you've added your new preference to the list above in alphabetical order.
   // Please do not just append it to the end of the list.
 
 public:
--- a/gfx/webrender/examples/nested_display_list.rs
+++ b/gfx/webrender/examples/nested_display_list.rs
@@ -31,17 +31,18 @@ fn body(_api: &RenderApi,
 
     let outer_scroll_frame_rect = (100, 100).to(600, 400);
     builder.push_rect(outer_scroll_frame_rect, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
     let nested_clip_id = builder.define_scroll_frame(None,
                                                      (100, 100).to(1000, 1000),
                                                      outer_scroll_frame_rect,
                                                      vec![],
-                                                     None);
+                                                     None,
+                                                     ScrollSensitivity::ScriptAndInputEvents);
     builder.push_clip_id(nested_clip_id);
 
     let mut builder2 = DisplayListBuilder::new(*pipeline_id, *layout_size);
     let mut builder3 = DisplayListBuilder::new(*pipeline_id, *layout_size);
 
     let rect = (110, 110).to(210, 210);
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
@@ -58,21 +59,23 @@ fn body(_api: &RenderApi,
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
     builder3.pop_stacking_context();
 
     // Now we push an inner scroll frame that should have the same id as the outer one,
     // but the WebRender nested display list replacement code should convert it into
     // a unique ClipId.
     let inner_scroll_frame_rect = (330, 110).to(530, 360);
     builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
-    let inner_nested_clip_id = builder3.define_scroll_frame(None,
-                                                            (330, 110).to(2000, 2000),
-                                                            inner_scroll_frame_rect,
-                                                            vec![],
-                                                            None);
+    let inner_nested_clip_id =
+        builder3.define_scroll_frame(None,
+                                     (330, 110).to(2000, 2000),
+                                     inner_scroll_frame_rect,
+                                     vec![],
+                                     None,
+                                     ScrollSensitivity::ScriptAndInputEvents);
     builder3.push_clip_id(inner_nested_clip_id);
     let rect = (340, 120).to(440, 220);
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
     builder3.pop_clip_id();
 
     let (_, _, built_list) = builder3.finalize();
     builder2.push_nested_display_list(&built_list);
     let (_, _, built_list) = builder2.finalize();
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -40,17 +40,18 @@ fn body(_api: &RenderApi,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
         // set the scrolling clip
         let clip_id = builder.define_scroll_frame(None,
                                                   (0, 0).by(1000, 1000),
                                                   scrollbox,
                                                   vec![],
-                                                  None);
+                                                  None,
+                                                  ScrollSensitivity::ScriptAndInputEvents);
         builder.push_clip_id(clip_id);
 
         // now put some content into it.
         // start with a white background
         builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         // let's make a 50x50 blue square as a visual reference
         builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));
@@ -63,17 +64,18 @@ fn body(_api: &RenderApi,
 
         // Below the above rectangles, set up a nested scrollbox. It's still in
         // the same stacking context, so note that the rects passed in need to
         // be relative to the stacking context.
         let nested_clip_id = builder.define_scroll_frame(None,
                                                          (0, 100).to(300, 400),
                                                          (0, 100).to(200, 300),
                                                          vec![],
-                                                         None);
+                                                         None,
+                                                         ScrollSensitivity::ScriptAndInputEvents);
         builder.push_clip_id(nested_clip_id);
 
         // give it a giant gray background just to distinguish it and to easily
         // visually identify the nested scrollbox
         builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
         // add a teal square to visualize the scrolling/clipping behaviour
         // as you scroll the nested scrollbox with WASD keys
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -49,16 +49,21 @@
 #define BORDER_STYLE_OUTSET       9
 
 #define UV_NORMALIZED    uint(0)
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
+#define LINE_STYLE_SOLID        0
+#define LINE_STYLE_DOTTED       1
+#define LINE_STYLE_DASHED       2
+#define LINE_STYLE_WAVY         3
+
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 uniform sampler2D sGradients;
 
 struct RectWithSize {
     vec2 p0;
     vec2 size;
@@ -778,16 +783,27 @@ struct TextShadow {
     float blur_radius;
 };
 
 TextShadow fetch_text_shadow(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return TextShadow(data[0], data[1].xy, data[1].z);
 }
 
+struct Line {
+    vec4 color;
+    float style;
+    float orientation;
+};
+
+Line fetch_line(int address) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    return Line(data[0], data[1].x, data[1].y);
+}
+
 struct TextRun {
     vec4 color;
     vec2 offset;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return TextRun(data[0], data[1].xy);
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.fs.glsl
@@ -0,0 +1,123 @@
+#line 1
+/* 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/. */
+
+float det(vec2 a, vec2 b) {
+    return a.x * b.y - b.x * a.y;
+}
+
+// From: http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
+vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
+    float a = det(b0, b2);
+    float b = 2.0 * det(b1, b0);
+    float d = 2.0 * det(b2, b1);
+
+    float f = b * d - a * a;
+    vec2 d21 = b2 - b1;
+    vec2 d10 = b1 - b0;
+    vec2 d20 = b2 - b0;
+
+    vec2 gf = 2.0 * (b *d21 + d * d10 + a * d20);
+    gf = vec2(gf.y,-gf.x);
+    vec2 pp = -f * gf / dot(gf, gf);
+    vec2 d0p = b0 - pp;
+    float ap = det(d0p, d20);
+    float bp = 2.0 * det(d10, d0p);
+
+    float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
+    return mix(mix(b0, b1, t), mix(b1,b2,t), t);
+}
+
+// Approximate distance from point to quadratic bezier.
+float approx_distance(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
+    return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
+}
+
+void main(void) {
+    float alpha = 1.0;
+
+#ifdef WR_FEATURE_CACHE
+    vec2 local_pos = vLocalPos;
+#else
+    #ifdef WR_FEATURE_TRANSFORM
+        alpha = 0.0;
+        vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+    #else
+        vec2 local_pos = vLocalPos;
+    #endif
+
+        alpha = min(alpha, do_clip());
+#endif
+
+    // Find the appropriate distance to apply the step over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // Select the x/y coord, depending on which axis this edge is.
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Calculate dash alpha (on/off) based on dash length
+            alpha = min(alpha, step(x, vParams.y));
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Get the dot alpha
+            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
+            float dot_distance = length(dot_relative_pos) - vParams.y;
+            alpha = min(alpha, 1.0 - smoothstep(-0.5 * afwidth,
+                                                0.5 * afwidth,
+                                                dot_distance));
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
+
+            float y0 = vParams.y;
+            float dy = vParams.z;
+            float dx = vParams.w;
+
+            // Flip the position of the bezier center points each
+            // wave period.
+            dy *= step(mod(normalized_local_pos.x, 4.0 * dx), 2.0 * dx) * 2.0 - 1.0;
+
+            // Convert pos to a local position within one wave period.
+            normalized_local_pos.x = dx + mod(normalized_local_pos.x, 2.0 * dx);
+
+            // Evaluate SDF to the first bezier.
+            vec2 b0_0 = vec2(0.0 * dx,  y0);
+            vec2 b1_0 = vec2(1.0 * dx,  y0 - dy);
+            vec2 b2_0 = vec2(2.0 * dx,  y0);
+            float d1 = approx_distance(normalized_local_pos, b0_0, b1_0, b2_0);
+
+            // Evaluate SDF to the second bezier.
+            vec2 b0_1 = vec2(2.0 * dx,  y0);
+            vec2 b1_1 = vec2(3.0 * dx,  y0 + dy);
+            vec2 b2_1 = vec2(4.0 * dx,  y0);
+            float d2 = approx_distance(normalized_local_pos, b0_1, b1_1, b2_1);
+
+            // SDF union - this is needed to avoid artifacts where the
+            // bezier curves join.
+            float d = min(d1, d2);
+
+            // Apply AA based on the thickness of the wave.
+            alpha = 1.0 - smoothstep(vParams.x - 0.5 * afwidth,
+                                     vParams.x + 0.5 * afwidth,
+                                     d);
+            break;
+        }
+    }
+
+    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.glsl
@@ -0,0 +1,15 @@
+/* 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/. */
+
+varying vec4 vColor;
+flat varying int vStyle;
+flat varying float vAxisSelect;
+flat varying vec4 vParams;
+flat varying vec2 vLocalOrigin;
+
+#ifdef WR_FEATURE_TRANSFORM
+varying vec3 vLocalPos;
+#else
+varying vec2 vLocalPos;
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.vs.glsl
@@ -0,0 +1,116 @@
+#line 1
+/* 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/. */
+
+#define LINE_ORIENTATION_VERTICAL       0
+#define LINE_ORIENTATION_HORIZONTAL     1
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Line line = fetch_line(prim.specific_prim_address);
+
+    vec2 pos, size;
+
+    switch (int(line.orientation)) {
+        case LINE_ORIENTATION_HORIZONTAL:
+            vAxisSelect = 0.0;
+            pos = prim.local_rect.p0;
+            size = prim.local_rect.size;
+            break;
+        case LINE_ORIENTATION_VERTICAL:
+            vAxisSelect = 1.0;
+            pos = prim.local_rect.p0.yx;
+            size = prim.local_rect.size.yx;
+            break;
+    }
+
+    vLocalOrigin = pos;
+    vStyle = int(line.style);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // y = dash on + off length
+            // z = dash length
+            // w = center line of edge cross-axis (for dots only)
+            float desired_dash_length = size.y * 3.0;
+            // Consider half total length since there is an equal on/off for each dash.
+            float dash_count = 1.0 + ceil(size.x / desired_dash_length);
+            float dash_length = size.x / dash_count;
+            vParams = vec4(2.0 * dash_length,
+                           dash_length,
+                           0.0,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            float diameter = size.y;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * size.x / diameter);
+            float empty_space = size.x - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            float center_line = pos.y + 0.5 * size.y;
+            vParams = vec4(distance_between_centers,
+                           radius,
+                           center_line,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            // Choose some arbitrary values to scale thickness,
+            // wave period etc.
+            // TODO(gw): Tune these to get closer to what Gecko uses.
+            float thickness = 0.2 * size.y;
+            vParams = vec4(thickness,
+                           size.y * 0.5,
+                           size.y * 0.75,
+                           max(3.0, thickness * 2.0));
+            break;
+        }
+    }
+
+#ifdef WR_FEATURE_CACHE
+    int text_shadow_address = prim.user_data0;
+    PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
+    TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
+
+    vec2 device_origin = prim.task.render_target_origin +
+                         uDevicePixelRatio * (prim.local_rect.p0 + shadow.offset - shadow_geom.local_rect.p0);
+    vec2 device_size = uDevicePixelRatio * prim.local_rect.size;
+
+    vec2 device_pos = mix(device_origin,
+                          device_origin + device_size,
+                          aPosition.xy);
+
+    vColor = shadow.color;
+    vLocalPos = mix(prim.local_rect.p0,
+                    prim.local_rect.p0 + prim.local_rect.size,
+                    aPosition.xy);
+
+    gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
+#else
+    vColor = line.color;
+
+    #ifdef WR_FEATURE_TRANSFORM
+        TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
+                                                        prim.local_clip_rect,
+                                                        prim.z,
+                                                        prim.layer,
+                                                        prim.task,
+                                                        prim.local_rect);
+    #else
+        VertexInfo vi = write_vertex(prim.local_rect,
+                                     prim.local_clip_rect,
+                                     prim.z,
+                                     prim.layer,
+                                     prim.task,
+                                     prim.local_rect);
+    #endif
+
+    vLocalPos = vi.local_pos;
+    write_clip(vi.screen_pos, prim.clip_area);
+#endif
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, WorldPoint};
+use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, WorldPoint};
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipRegion, ClipSource, MaskCacheInfo};
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
@@ -117,30 +117,31 @@ pub struct ClipScrollNode {
     /// Whether or not this node is a reference frame.
     pub node_type: NodeType,
 }
 
 impl ClipScrollNode {
     pub fn new_scroll_frame(pipeline_id: PipelineId,
                             parent_id: ClipId,
                             frame_rect: &LayerRect,
-                            content_size: &LayerSize)
+                            content_size: &LayerSize,
+                            scroll_sensitivity: ScrollSensitivity)
                             -> ClipScrollNode {
         ClipScrollNode {
             content_size: *content_size,
             local_viewport_rect: *frame_rect,
             local_clip_rect: *frame_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id,
-            node_type: NodeType::ScrollFrame(ScrollingState::new()),
+            node_type: NodeType::ScrollFrame(ScrollingState::new(scroll_sensitivity)),
         }
     }
 
     pub fn new(pipeline_id: PipelineId, parent_id: ClipId, clip_info: ClipInfo) -> ClipScrollNode {
         ClipScrollNode {
             content_size: clip_info.clip_rect.size,
             local_viewport_rect: clip_info.clip_rect,
             local_clip_rect: clip_info.clip_rect,
@@ -175,24 +176,28 @@ impl ClipScrollNode {
             node_type: NodeType::ReferenceFrame(*local_transform),
         }
     }
 
     pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
-    pub fn finalize(&mut self, new_scrolling: &ScrollingState) {
+    pub fn apply_old_scrolling_state(&mut self, new_scrolling: &ScrollingState) {
         match self.node_type {
             NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
                 if new_scrolling.offset != LayerVector2D::zero() {
                     warn!("Tried to scroll a non-scroll node.");
                 }
             }
-            NodeType::ScrollFrame(ref mut scrolling) => *scrolling = *new_scrolling,
+            NodeType::ScrollFrame(ref mut scrolling) => {
+                let scroll_sensitivity = scrolling.scroll_sensitivity;
+                *scrolling = *new_scrolling;
+                scrolling.scroll_sensitivity = scroll_sensitivity;
+            }
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
         let scrollable_height = self.scrollable_height();
         let scrollable_width = self.scrollable_width();
 
         let scrolling = match self.node_type {
@@ -399,28 +404,37 @@ impl ClipScrollNode {
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollingState {
     pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
-    pub should_handoff_scroll: bool
+    pub should_handoff_scroll: bool,
+    pub scroll_sensitivity: ScrollSensitivity,
 }
 
 /// Manages scrolling offset, overscroll state, etc.
 impl ScrollingState {
-    pub fn new() -> ScrollingState {
+    pub fn new(scroll_sensitivity: ScrollSensitivity) -> ScrollingState {
         ScrollingState {
             offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
-            should_handoff_scroll: false
+            should_handoff_scroll: false,
+            scroll_sensitivity,
+        }
+    }
+
+    pub fn sensitive_to_input_events(&self) -> bool {
+        match self.scroll_sensitivity {
+            ScrollSensitivity::ScriptAndInputEvents => true,
+            ScrollSensitivity::Script => false,
         }
     }
 
     pub fn stretch_overscroll_spring(&mut self, overscroll_amount: LayerVector2D) {
         let offset = self.offset.to_point();
         self.spring.coords(offset, offset, offset + overscroll_amount);
     }
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -88,17 +88,17 @@ impl ClipScrollTree {
             for child_layer_id in node.children.iter().rev() {
                 if let Some(layer_id) =
                    self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
                     return Some(layer_id);
                 }
             }
 
             match node.node_type {
-                NodeType::ScrollFrame(..) => {},
+                NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {},
                 _ => return None,
             }
 
             if node.ray_intersects_node(cursor) {
                 Some(clip_id)
             } else {
                 None
             }
@@ -298,22 +298,19 @@ impl ClipScrollTree {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         // TODO(gw): These are all independent - can be run through thread pool if it shows up
         // in the profile!
         for (clip_id, node) in &mut self.nodes {
-            let scrolling_state = match old_states.get(clip_id) {
-                Some(old_scrolling_state) => *old_scrolling_state,
-                None => ScrollingState::new(),
-            };
-
-            node.finalize(&scrolling_state);
+            if let Some(scrolling_state) = old_states.get(clip_id) {
+                node.apply_old_scrolling_state(scrolling_state);
+            }
 
             if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
                 node.set_scroll_origin(&pending_offset, clamping);
             }
         }
     }
 
     pub fn generate_new_clip_id(&mut self, pipeline_id: PipelineId) -> ClipId {
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -2,19 +2,19 @@
  * 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/. */
 
 use debug_font_data;
 use device::{Device, GpuMarker, ProgramId, VAOId, TextureId, VertexFormat};
 use device::{TextureFilter, VertexUsageHint, TextureTarget};
 use euclid::{Transform3D, Point2D, Size2D, Rect};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, TextureSampler};
-use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode, PackedColor};
+use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode};
 use std::f32;
-use api::{ColorF, ImageFormat, DeviceUintSize};
+use api::{ColorU, ImageFormat, DeviceUintSize};
 
 pub struct DebugRenderer {
     font_vertices: Vec<DebugFontVertex>,
     font_indices: Vec<u32>,
     font_program_id: ProgramId,
     font_vao: VAOId,
     font_texture_id: TextureId,
 
@@ -62,21 +62,20 @@ impl DebugRenderer {
     pub fn line_height(&self) -> f32 {
         debug_font_data::FONT_SIZE as f32 * 1.1
     }
 
     pub fn add_text(&mut self,
                     x: f32,
                     y: f32,
                     text: &str,
-                    color: &ColorF) -> Rect<f32> {
+                    color: ColorU) -> Rect<f32> {
         let mut x_start = x;
         let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32;
         let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32;
-        let color = PackedColor::from_color(color);
 
         let mut min_x = f32::MAX;
         let mut max_x = -f32::MAX;
         let mut min_y = f32::MAX;
         let mut max_y = -f32::MAX;
 
         for c in text.chars() {
             let c = c as usize - debug_font_data::FIRST_GLYPH_INDEX as usize;
@@ -120,20 +119,18 @@ impl DebugRenderer {
         Rect::new(Point2D::new(min_x, min_y), Size2D::new(max_x-min_x, max_y-min_y))
     }
 
     pub fn add_quad(&mut self,
                     x0: f32,
                     y0: f32,
                     x1: f32,
                     y1: f32,
-                    color_top: &ColorF,
-                    color_bottom: &ColorF) {
-        let color_top = PackedColor::from_color(color_top);
-        let color_bottom = PackedColor::from_color(color_bottom);
+                    color_top: ColorU,
+                    color_bottom: ColorU) {
         let vertex_count = self.tri_vertices.len() as u32;
 
         self.tri_vertices.push(DebugColorVertex::new(x0, y0, color_top));
         self.tri_vertices.push(DebugColorVertex::new(x1, y0, color_top));
         self.tri_vertices.push(DebugColorVertex::new(x0, y1, color_bottom));
         self.tri_vertices.push(DebugColorVertex::new(x1, y1, color_bottom));
 
         self.tri_indices.push(vertex_count + 0);
@@ -143,22 +140,20 @@ impl DebugRenderer {
         self.tri_indices.push(vertex_count + 1);
         self.tri_indices.push(vertex_count + 3);
     }
 
     #[allow(dead_code)]
     pub fn add_line(&mut self,
                     x0: i32,
                     y0: i32,
-                    color0: &ColorF,
+                    color0: ColorU,
                     x1: i32,
                     y1: i32,
-                    color1: &ColorF) {
-        let color0 = PackedColor::from_color(color0);
-        let color1 = PackedColor::from_color(color1);
+                    color1: ColorU) {
         self.line_vertices.push(DebugColorVertex::new(x0 as f32, y0 as f32, color0));
         self.line_vertices.push(DebugColorVertex::new(x1 as f32, y1 as f32, color1));
     }
 
     pub fn render(&mut self,
                   device: &mut Device,
                   viewport_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(device.rc_gl(), "debug");
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,25 +1,23 @@
 /* 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/. */
 
 use api::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
 use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
 use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation, ScrollPolicy};
-use api::{SpecificDisplayItem, StackingContext, TileOffset, TransformStyle, WorldPoint};
-use app_units::Au;
+use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
+use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
+use api::{TransformStyle, WorldPoint};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use fnv::FnvHasher;
 use gpu_cache::GpuCache;
-use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
-use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use mask_cache::ClipRegion;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::cmp;
 use std::collections::HashMap;
@@ -185,69 +183,41 @@ pub struct Frame {
     frame_builder: Option<FrameBuilder>,
 }
 
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
-                                  properties: &SceneProperties) -> Vec<LowLevelFilterOp>;
+                                  properties: &SceneProperties) -> Vec<FilterOp>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
     }
 
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
-                                  properties: &SceneProperties) -> Vec<LowLevelFilterOp> {
+                                  properties: &SceneProperties) -> Vec<FilterOp> {
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
             if filter.is_noop() {
                 continue;
             }
-
-            match filter {
-                FilterOp::Blur(radius) => {
-                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Horizontal));
-                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Vertical));
-                }
-                FilterOp::Brightness(amount) => {
-                    filters.push(LowLevelFilterOp::Brightness(Au::from_f32_px(amount)));
-                }
-                FilterOp::Contrast(amount) => {
-                    filters.push(LowLevelFilterOp::Contrast(Au::from_f32_px(amount)));
-                }
-                FilterOp::Grayscale(amount) => {
-                    filters.push(LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)));
-                }
-                FilterOp::HueRotate(angle) => {
-                    filters.push(
-                            LowLevelFilterOp::HueRotate(f32::round(
-                                    angle * ANGLE_FLOAT_TO_FIXED) as i32));
-                }
-                FilterOp::Invert(amount) => {
-                    filters.push(LowLevelFilterOp::Invert(Au::from_f32_px(amount)));
-                }
-                FilterOp::Opacity(ref value) => {
-                    let amount = properties.resolve_float(value, 1.0);
-                    filters.push(LowLevelFilterOp::Opacity(Au::from_f32_px(amount)));
-                }
-                FilterOp::Saturate(amount) => {
-                    filters.push(LowLevelFilterOp::Saturate(Au::from_f32_px(amount)));
-                }
-                FilterOp::Sepia(amount) => {
-                    filters.push(LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
-                }
+            if let FilterOp::Opacity(ref value) = filter {
+                let amount = properties.resolve_float(value, 1.0);
+                filters.push(FilterOp::Opacity(PropertyBinding::Value(amount)));
+            } else {
+                filters.push(filter);
             }
         }
         filters
     }
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
@@ -376,30 +346,32 @@ impl Frame {
 
     fn flatten_scroll_frame<'a>(&mut self,
                                 context: &mut FlattenContext,
                                 pipeline_id: PipelineId,
                                 parent_id: &ClipId,
                                 new_scroll_frame_id: &ClipId,
                                 frame_rect: &LayerRect,
                                 content_rect: &LayerRect,
-                                clip_region: ClipRegion) {
+                                clip_region: ClipRegion,
+                                scroll_sensitivity: ScrollSensitivity) {
         let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
         context.builder.add_clip_node(clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
 
         let new_scroll_frame_id = context.convert_new_id_to_nested(new_scroll_frame_id);
         context.builder.add_scroll_frame(new_scroll_frame_id,
                                          clip_id,
                                          pipeline_id,
                                          &frame_rect,
                                          &content_rect.size,
+                                         scroll_sensitivity,
                                          &mut self.clip_scroll_tree);
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
@@ -530,16 +502,17 @@ impl Frame {
                                                  &mut self.clip_scroll_tree);
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
+            ScrollSensitivity::ScriptAndInputEvents,
             &mut self.clip_scroll_tree);
 
         self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
 
         context.builder.pop_reference_frame();
     }
 
     fn flatten_item<'a, 'b>(&mut self,
@@ -550,124 +523,140 @@ impl Frame {
                             -> Option<BuiltDisplayListIter<'a>> {
         let mut clip_and_scroll = item.clip_and_scroll();
         context.convert_clip_scroll_info_to_nested(&mut clip_and_scroll);
 
         let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
         clip_and_scroll.scroll_node_id =
             context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
 
+
+        let item_rect_with_offset = item.rect().translate(&reference_frame_relative_offset);
+        let clip_with_offset = item.local_clip_with_offset(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::WebGL(ref info) => {
                 context.builder.add_webgl_rectangle(clip_and_scroll,
-                                                    item.rect(),
-                                                    item.local_clip(),
+                                                    item_rect_with_offset,
+                                                    &clip_with_offset,
                                                     info.context_id);
             }
             SpecificDisplayItem::Image(ref info) => {
                 let image = context.resource_cache.get_image_properties(info.image_key);
                 if let Some(tile_size) = image.tiling {
                     // The image resource is tiled. We have to generate an image primitive
                     // for each tile.
                     let image_size = DeviceUintSize::new(image.descriptor.width,
                                                          image.descriptor.height);
                     self.decompose_image(clip_and_scroll,
                                          context,
-                                         &item.rect(),
-                                         item.local_clip(),
+                                         &item_rect_with_offset,
+                                         &clip_with_offset,
                                          info,
                                          image_size,
                                          tile_size as u32);
                 } else {
                     context.builder.add_image(clip_and_scroll,
-                                              item.rect(),
-                                              item.local_clip(),
+                                              item_rect_with_offset,
+                                              &clip_with_offset,
                                               &info.stretch_size,
                                               &info.tile_spacing,
                                               None,
                                               info.image_key,
                                               info.image_rendering,
                                               None);
                 }
             }
             SpecificDisplayItem::YuvImage(ref info) => {
                 context.builder.add_yuv_image(clip_and_scroll,
-                                              item.rect(),
-                                              item.local_clip(),
+                                              item_rect_with_offset,
+                                              &clip_with_offset,
                                               info.yuv_data,
                                               info.color_space,
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
                 context.builder.add_text(clip_and_scroll,
-                                         item.rect(),
-                                         item.local_clip(),
+                                         reference_frame_relative_offset,
+                                         item_rect_with_offset,
+                                         &clip_with_offset,
                                          text_info.font_key,
                                          text_info.size,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
-                                                                &item.rect(),
-                                                                item.local_clip(),
+                                                                &item_rect_with_offset,
+                                                                &clip_with_offset,
                                                                 &info.color,
                                                                 &clip_and_scroll) {
                     context.builder.add_solid_rectangle(clip_and_scroll,
-                                                        &item.rect(),
-                                                        item.local_clip(),
+                                                        &item_rect_with_offset,
+                                                        &clip_with_offset,
                                                         &info.color,
                                                         PrimitiveFlags::None);
 
                 }
             }
+            SpecificDisplayItem::Line(ref info) => {
+                context.builder.add_line(clip_and_scroll,
+                                         item.local_clip(),
+                                         info.baseline,
+                                         info.start,
+                                         info.end,
+                                         info.orientation,
+                                         info.width,
+                                         &info.color,
+                                         info.style);
+            }
             SpecificDisplayItem::Gradient(ref info) => {
                 context.builder.add_gradient(clip_and_scroll,
-                                             item.rect(),
-                                             item.local_clip(),
+                                             item_rect_with_offset,
+                                             &clip_with_offset,
                                              info.gradient.start_point,
                                              info.gradient.end_point,
                                              item.gradient_stops(),
                                              item.display_list()
                                                  .get(item.gradient_stops()).count(),
                                              info.gradient.extend_mode,
                                              info.tile_size,
                                              info.tile_spacing);
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 context.builder.add_radial_gradient(clip_and_scroll,
-                                                    item.rect(),
-                                                    item.local_clip(),
+                                                    item_rect_with_offset,
+                                                    &clip_with_offset,
                                                     info.gradient.start_center,
                                                     info.gradient.start_radius,
                                                     info.gradient.end_center,
                                                     info.gradient.end_radius,
                                                     info.gradient.ratio_xy,
                                                     item.gradient_stops(),
                                                     info.gradient.extend_mode,
                                                     info.tile_size,
                                                     info.tile_spacing);
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+                let bounds = box_shadow_info.box_bounds.translate(&reference_frame_relative_offset);
                 context.builder.add_box_shadow(clip_and_scroll,
-                                               &box_shadow_info.box_bounds,
-                                               item.local_clip(),
+                                               &bounds,
+                                               &clip_with_offset,
                                                &box_shadow_info.offset,
                                                &box_shadow_info.color,
                                                box_shadow_info.blur_radius,
                                                box_shadow_info.spread_radius,
                                                box_shadow_info.border_radius,
                                                box_shadow_info.clip_mode);
             }
             SpecificDisplayItem::Border(ref info) => {
                 context.builder.add_border(clip_and_scroll,
-                                           item.rect(),
-                                           item.local_clip(),
+                                           item_rect_with_offset,
+                                           &clip_with_offset,
                                            info,
                                            item.gradient_stops(),
                                            item.display_list()
                                                .get(item.gradient_stops()).count());
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(&mut subtraversal,
@@ -718,17 +707,18 @@ impl Frame {
                                      .translate(&reference_frame_relative_offset);
                 let content_rect = item.rect().translate(&reference_frame_relative_offset);
                 self.flatten_scroll_frame(context,
                                           pipeline_id,
                                           &clip_and_scroll.scroll_node_id,
                                           &info.id,
                                           &frame_rect,
                                           &content_rect,
-                                          clip_region);
+                                          clip_region,
+                                          info.scroll_sensitivity);
             }
             SpecificDisplayItem::PushNestedDisplayList => {
                 // Using the clip and scroll already processed for nesting here
                 // means that in the case of multiple nested display lists, we
                 // will enter the outermost ids into the table and avoid having
                 // to do a replacement for every level of nesting.
                 context.push_nested_display_list_ids(clip_and_scroll);
             }
@@ -737,17 +727,17 @@ impl Frame {
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => { }
 
             SpecificDisplayItem::PopStackingContext =>
                 unreachable!("Should have returned in parent method."),
             SpecificDisplayItem::PushTextShadow(shadow) => {
                 context.builder.push_text_shadow(shadow,
                                                  clip_and_scroll,
-                                                 item.local_clip());
+                                                 &clip_with_offset);
             }
             SpecificDisplayItem::PopTextShadow => {
                 context.builder.pop_text_shadow();
             }
         }
         None
     }
 
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,37 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
-use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
+use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, TextShadow, TileOffset};
+use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
+use fnv::FnvHasher;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::HardwareCompositeOp;
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, PrimitiveKind};
+use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use std::collections::HashMap;
+use std::hash::BuildHasherDefault;
 use euclid::{SideOffsets2D, vec2, vec3};
 use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::{MatrixHelpers, RectHelpers};
 
@@ -110,16 +113,19 @@ pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
+    clip_scroll_group_indices: HashMap<ClipAndScrollInfo,
+                                       ClipScrollGroupIndex,
+                                       BuildHasherDefault<FnvHasher>>,
     packed_layers: Vec<PackedLayer>,
 
     // A stack of the current text-shadow primitives.
     shadow_prim_stack: Vec<PrimitiveIndex>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
@@ -140,16 +146,17 @@ impl FrameBuilder {
                screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
             Some(prev) => {
                 FrameBuilder {
                     stacking_context_store: recycle_vec(prev.stacking_context_store),
                     clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
+                    clip_scroll_group_indices: HashMap::default(),
                     cmds: recycle_vec(prev.cmds),
                     packed_layers: recycle_vec(prev.packed_layers),
                     shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
                     screen_size,
@@ -157,56 +164,52 @@ impl FrameBuilder {
                     config,
                     has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
+                    clip_scroll_group_indices: HashMap::default(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
                     shadow_prim_stack: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
         }
     }
 
-    pub fn create_clip_scroll_group_if_necessary(&mut self,
-                                                 stacking_context_index: StackingContextIndex,
-                                                 info: ClipAndScrollInfo) {
-        if self.stacking_context_store[stacking_context_index.0].has_clip_scroll_group(info) {
+    pub fn create_clip_scroll_group_if_necessary(&mut self, info: ClipAndScrollInfo) {
+        if self.clip_scroll_group_indices.contains_key(&info) {
             return;
         }
 
-        let group_index = self.create_clip_scroll_group(stacking_context_index, info);
-        let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
-        stacking_context.clip_scroll_groups.push(group_index);
+        let group_index = self.create_clip_scroll_group(info);
+        self.clip_scroll_group_indices.insert(info, group_index);
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     fn create_primitive(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
                         rect: &LayerRect,
                         local_clip: &LocalClip,
                         extra_clips: &[ClipSource],
                         container: PrimitiveContainer) -> PrimitiveIndex {
-        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-
-        self.create_clip_scroll_group_if_necessary(stacking_context_index, clip_and_scroll);
+        self.create_clip_scroll_group_if_necessary(clip_and_scroll);
 
         let mut clip_sources = extra_clips.to_vec();
         if let &LocalClip::RoundedRect(_, _) = local_clip {
             clip_sources.push(ClipSource::Region(ClipRegion::create_for_local_clip(local_clip)))
         }
 
         let clip_info = if !clip_sources.is_empty() {
             Some(MaskCacheInfo::new(&clip_sources))
@@ -256,25 +259,21 @@ impl FrameBuilder {
                                                extra_clips,
                                                container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
 
         prim_index
     }
 
-    pub fn create_clip_scroll_group(&mut self,
-                                    stacking_context_index: StackingContextIndex,
-                                    info: ClipAndScrollInfo)
-                                    -> ClipScrollGroupIndex {
+    pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
         let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
         self.packed_layers.push(PackedLayer::empty());
 
         self.clip_scroll_group_store.push(ClipScrollGroup {
-            stacking_context_index,
             scroll_node_id: info.scroll_node_id,
             clip_node_id: info.clip_node_id(),
             packed_layer_index,
             screen_bounding_rect: None,
          });
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
@@ -385,16 +384,17 @@ impl FrameBuilder {
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(topmost_scrolling_node_id,
                               clip_scroll_tree.root_reference_frame_id,
                               pipeline_id,
                               &viewport_rect,
                               content_size,
+                              ScrollSensitivity::ScriptAndInputEvents,
                               clip_scroll_tree);
 
         topmost_scrolling_node_id
     }
 
     pub fn add_clip_node(&mut self,
                          new_node_id: ClipId,
                          parent_id: ClipId,
@@ -408,21 +408,23 @@ impl FrameBuilder {
     }
 
     pub fn add_scroll_frame(&mut self,
                             new_node_id: ClipId,
                             parent_id: ClipId,
                             pipeline_id: PipelineId,
                             frame_rect: &LayerRect,
                             content_size: &LayerSize,
+                            scroll_sensitivity: ScrollSensitivity,
                             clip_scroll_tree: &mut ClipScrollTree) {
         let node = ClipScrollNode::new_scroll_frame(pipeline_id,
                                                     parent_id,
                                                     frame_rect,
-                                                    content_size);
+                                                    content_size,
+                                                    scroll_sensitivity);
 
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
@@ -464,62 +466,104 @@ impl FrameBuilder {
     }
 
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: &LayerRect,
                                local_clip: &LocalClip,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
-        // TODO(gw): This is here as a temporary measure to allow
-        //           solid rectangles to be drawn into an
-        //           (unblurred) text-shadow. Supporting this allows
-        //           a WR update in Servo, since the tests rely
-        //           on this functionality. Once the complete
-        //           text decoration support is added (via the
-        //           Line display item) this can be removed, so that
-        //           rectangles don't participate in text shadows.
-        let mut trivial_shadows = Vec::new();
+        let prim = RectanglePrimitive {
+            color: *color,
+        };
+
+        let prim_index = self.add_primitive(clip_and_scroll,
+                                            rect,
+                                            local_clip,
+                                            &[],
+                                            PrimitiveContainer::Rectangle(prim));
+
+        match flags {
+            PrimitiveFlags::None => {}
+            PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
+                self.scrollbar_prims.push(ScrollbarPrimitive {
+                    prim_index,
+                    clip_id,
+                    border_radius,
+                });
+            }
+        }
+    }
+
+    pub fn add_line(&mut self,
+                    clip_and_scroll: ClipAndScrollInfo,
+                    local_clip: &LocalClip,
+                    baseline: f32,
+                    start: f32,
+                    end: f32,
+                    orientation: LineOrientation,
+                    width: f32,
+                    color: &ColorF,
+                    style: LineStyle) {
+        let new_rect = match orientation {
+            LineOrientation::Horizontal => {
+                LayerRect::new(LayerPoint::new(start, baseline),
+                               LayerSize::new(end - start, width))
+            }
+            LineOrientation::Vertical => {
+                LayerRect::new(LayerPoint::new(baseline, start),
+                               LayerSize::new(width, end - start))
+            }
+        };
+
+        let line = LinePrimitive {
+            color: *color,
+            style: style,
+            orientation: orientation,
+        };
+
+        let mut fast_text_shadow_prims = Vec::new();
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
-                trivial_shadows.push(shadow_prim.shadow);
+                fast_text_shadow_prims.push(shadow_prim.shadow);
             }
         }
-        for shadow in trivial_shadows {
+        for shadow in fast_text_shadow_prims {
+            let mut line = line.clone();
+            line.color = shadow.color;
             self.add_primitive(clip_and_scroll,
-                               &rect.translate(&shadow.offset),
+                               &new_rect.translate(&shadow.offset),
                                local_clip,
                                &[],
-                               PrimitiveContainer::Rectangle(RectanglePrimitive {
-                                   color: shadow.color,
-                               }));
+                               PrimitiveContainer::Line(line));
         }
 
+        let prim_index = self.create_primitive(clip_and_scroll,
+                                               &new_rect,
+                                               local_clip,
+                                               &[],
+                                               PrimitiveContainer::Line(line));
+
         if color.a > 0.0 {
-            let prim = RectanglePrimitive {
-                color: *color,
-            };
-
-            let prim_index = self.add_primitive(clip_and_scroll,
-                                                rect,
-                                                local_clip,
-                                                &[],
-                                                PrimitiveContainer::Rectangle(prim));
+            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+        }
 
-            match flags {
-                PrimitiveFlags::None => {}
-                PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
-                    self.scrollbar_prims.push(ScrollbarPrimitive {
-                        prim_index,
-                        clip_id,
-                        border_radius,
-                    });
-                }
+        for shadow_prim_index in &self.shadow_prim_stack {
+            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
+            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::TextShadow);
+            let shadow_prim = &mut self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
+
+            // Only run real blurs here (fast path zero blurs are handled above).
+            if shadow_prim.shadow.blur_radius > 0.0 {
+                let shadow_rect = new_rect.inflate(shadow_prim.shadow.blur_radius,
+                                                   shadow_prim.shadow.blur_radius);
+                shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect);
+                shadow_prim.primitives.push(prim_index);
             }
         }
     }
 
     pub fn add_border(&mut self,
                       clip_and_scroll: ClipAndScrollInfo,
                       rect: LayerRect,
                       local_clip: &LocalClip,
@@ -818,16 +862,17 @@ impl FrameBuilder {
                            &rect,
                            local_clip,
                            &[],
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
+                    run_offset: LayoutVector2D,
                     rect: LayerRect,
                     local_clip: &LocalClip,
                     font_key: FontKey,
                     size: Au,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
@@ -872,17 +917,17 @@ impl FrameBuilder {
             font_key,
             logical_font_size: size,
             glyph_range,
             glyph_count,
             glyph_instances: Vec::new(),
             glyph_options,
             normal_render_mode,
             shadow_render_mode,
-            offset: LayerVector2D::zero(),
+            offset: run_offset,
             color: *color,
         };
 
         // Text shadows that have a blur radius of 0 need to be rendered as normal
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
@@ -891,17 +936,17 @@ impl FrameBuilder {
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_text_shadow_prims = Vec::new();
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
                 let mut text_prim = prim.clone();
                 text_prim.color = shadow_prim.shadow.color;
-                text_prim.offset = shadow_prim.shadow.offset;
+                text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
             self.add_primitive(clip_and_scroll,
                                &rect.translate(&text_prim.offset),
                                local_clip,
                                &[],
@@ -1201,21 +1246,21 @@ impl FrameBuilder {
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: LayerRect,
                          clip_rect: &LocalClip,
                          yuv_data: YuvData,
                          color_space: YuvColorSpace,
                          image_rendering: ImageRendering) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
-            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
+            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::dummy()],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
                 [plane_0, plane_1, plane_2],
             YuvData::InterleavedYCbCr(plane_0) =>
-                [plane_0, ImageKey::new(0, 0), ImageKey::new(0, 0)],
+                [plane_0, ImageKey::dummy(), ImageKey::dummy()],
         };
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key,
             format,
             color_space,
             image_rendering,
             gpu_block: [rect.size.width, rect.size.height, 0.0, 0.0].into(),
@@ -1460,25 +1505,21 @@ impl FrameBuilder {
                             current_task.as_alpha_batch().items.push(item);
                         }
                         preserve_3d_map.clear();
                         next_z += 1;
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
-                    let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-
-                    if !stacking_context.is_visible {
+                    if !self.stacking_context_store[stacking_context_index.0].is_visible {
                         continue;
                     }
 
-                    let stacking_context_index = *sc_stack.last().unwrap();
-                    let group_index = self.stacking_context_store[stacking_context_index.0]
-                                          .clip_scroll_group(clip_and_scroll);
+                    let group_index = *self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
                     if self.clip_scroll_group_store[group_index.0].screen_bounding_rect.is_none() {
                         debug!("\tcs-group {:?} screen rect is None", group_index);
                         continue
                     }
 
                     debug!("\trun of {} items into {:?}", prim_count, current_task.id);
 
                     for i in 0..prim_count {
@@ -1659,17 +1700,16 @@ impl<'a> LayerRectCalculationAndCullingP
             current_clip_info: None,
         };
         pass.run();
     }
 
     fn run(&mut self) {
         self.recalculate_clip_scroll_nodes();
         self.recalculate_clip_scroll_groups();
-        self.compute_stacking_context_visibility();
 
         debug!("processing commands...");
         let commands = mem::replace(&mut self.frame_builder.cmds, Vec::new());
         for cmd in &commands {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) =>
                     self.handle_push_stacking_context(stacking_context_index),
                 PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, clip_and_scroll) =>
@@ -1733,63 +1773,43 @@ impl<'a> LayerRectCalculationAndCullingP
                 }
             }
         }
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         debug!("recalculate_clip_scroll_groups");
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
-            let stacking_context_index = group.stacking_context_index;
-            let stacking_context = &mut self.frame_builder
-                                            .stacking_context_store[stacking_context_index.0];
-
             let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
             let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
             let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
 
-            // The world content transform is relative to the containing reference frame,
-            // so we translate into the origin of the stacking context itself.
-            let transform = scroll_node.world_content_transform
-                .pre_translate(stacking_context.reference_frame_offset.to_3d());
+            debug!("\tProcessing group scroll={:?}, clip={:?}",
+                   group.scroll_node_id, group.clip_node_id);
 
-            if !packed_layer.set_transform(transform) || !stacking_context.can_contribute_to_scene() {
-                debug!("\t{:?} unable to set transform or contribute with {:?}",
-                    stacking_context_index, transform);
+            let transform = scroll_node.world_content_transform;
+            if !packed_layer.set_transform(transform) {
+                debug!("\t\tUnable to set transform {:?}", transform);
                 return;
             }
 
             // Here we move the viewport rectangle into the coordinate system
             // of the stacking context content.
             let local_viewport_rect = clip_node.combined_local_viewport_rect
                 .translate(&clip_node.reference_frame_relative_scroll_offset)
                 .translate(&-scroll_node.reference_frame_relative_scroll_offset)
-                .translate(&-stacking_context.reference_frame_offset)
                 .translate(&-scroll_node.scroll_offset());
 
             group.screen_bounding_rect = packed_layer.set_rect(&local_viewport_rect,
                                                                self.screen_rect,
                                                                self.device_pixel_ratio);
 
-            debug!("\t{:?} local viewport {:?} screen bound {:?}",
-                stacking_context_index, local_viewport_rect, group.screen_bounding_rect);
-        }
-    }
-
-    fn compute_stacking_context_visibility(&mut self) {
-        for context_index in 0..self.frame_builder.stacking_context_store.len() {
-            let is_visible = {
-                // We don't take into account visibility of children here, so we must
-                // do that later.
-                let stacking_context = &self.frame_builder.stacking_context_store[context_index];
-                stacking_context.clip_scroll_groups.iter().any(|group_index| {
-                    self.frame_builder.clip_scroll_group_store[group_index.0].is_visible()
-                })
-            };
-            self.frame_builder.stacking_context_store[context_index].is_visible = is_visible;
+            debug!("\t\tlocal viewport {:?} screen bound {:?}",
+                   local_viewport_rect,
+                   group.screen_bounding_rect);
         }
     }
 
     fn handle_pop_stacking_context(&mut self) {
         let stacking_context_index = self.stacking_context_stack.pop().unwrap();
 
         let (bounding_rect, is_visible, is_preserve_3d, reference_frame_id, reference_frame_bounds) = {
             let stacking_context =
@@ -1808,18 +1828,18 @@ impl<'a> LayerRectCalculationAndCullingP
         if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
             let parent = &mut self.frame_builder.stacking_context_store[parent_index.0];
             parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
             // add children local bounds only for non-item-isolated contexts
             if !is_preserve_3d && parent.reference_frame_id == reference_frame_id {
                 let child_bounds = reference_frame_bounds.translate(&-parent.reference_frame_offset);
                 parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
             }
-            // The previous compute_stacking_context_visibility pass did not take into
-            // account visibility of children, so we do that now.
+            // Per-primitive stacking context visibility checks do not take into account
+            // visibility of child stacking contexts, so do that now.
             parent.is_visible = parent.is_visible || is_visible;
         }
     }
 
     fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
         self.stacking_context_stack.push(stacking_context_index);
 
         // Reset bounding rect to zero. We will calculate it as we collect primitives
@@ -1899,29 +1919,37 @@ impl<'a> LayerRectCalculationAndCullingP
 
     fn handle_primitive_run(&mut self,
                             base_prim_index: PrimitiveIndex,
                             prim_count: usize,
                             clip_and_scroll: ClipAndScrollInfo) {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
         let (packed_layer_index, pipeline_id) = {
             let stacking_context =
-                &self.frame_builder.stacking_context_store[stacking_context_index.0];
+                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
+            if !stacking_context.can_contribute_to_scene() {
+                return;
+            }
 
-            if !stacking_context.is_visible {
+            let group_index =
+                self.frame_builder.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
+            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
+            if !clip_scroll_group.is_visible() {
                 debug!("{:?} of invisible {:?}", base_prim_index, stacking_context_index);
                 return;
             }
 
-            let group_index = stacking_context.clip_scroll_group(clip_and_scroll);
-            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
-            (clip_scroll_group.packed_layer_index,
-             stacking_context.pipeline_id)
+            // At least one primitive in this stacking context is visible, so the stacking
+            // context is visible.
+            stacking_context.is_visible = true;
+
+            (clip_scroll_group.packed_layer_index, stacking_context.pipeline_id)
         };
 
+
         debug!("\t{:?} of {:?} at {:?}", base_prim_index, stacking_context_index, packed_layer_index);
         let clip_bounds = match self.rebuild_clip_info_stack_if_necessary(clip_and_scroll.clip_node_id()) {
             Some(rect) => rect,
             None => return,
         };
 
         let stacking_context =
             &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,32 +1,35 @@
 /* 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/. */
 
+#[cfg(test)]
 use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
 use platform::font::{FontContext, RasterizedGlyph};
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use resource_cache::ResourceClassCache;
 use std::hash::BuildHasherDefault;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::collections::hash_map::Entry;
 use std::collections::HashSet;
 use std::mem;
 use texture_cache::{TextureCacheItemId, TextureCache};
-use api::FontTemplate;
-use api::{FontKey, FontRenderMode, ImageData, ImageFormat};
-use api::{ImageDescriptor, ColorF, LayoutPoint};
-use api::{GlyphKey, GlyphOptions, GlyphInstance, GlyphDimensions};
+#[cfg(test)]
+use api::{ColorF, FontRenderMode, IdNamespace};
+use api::{FontInstanceKey, LayoutPoint};
+use api::{FontKey, FontTemplate};
+use api::{ImageData, ImageDescriptor, ImageFormat};
+use api::{GlyphKey, GlyphInstance, GlyphDimensions};
 
 pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
 
 pub struct FontContexts {
     // These worker are mostly accessed from their corresponding worker threads.
     // The goal is that there should be no noticeable contention on the muteces.
     worker_contexts: Vec<Mutex<FontContext>>,
 
@@ -142,50 +145,40 @@ impl GlyphRasterizer {
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         current_frame_id: FrameId,
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
+        font: FontInstanceKey,
         glyph_instances: &[GlyphInstance],
-        render_mode: FontRenderMode,
-        glyph_options: Option<GlyphOptions>,
         requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
     ) {
-        assert!(self.font_contexts.lock_shared_context().has_font(&font_key));
+        assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
 
         let mut glyphs = Vec::with_capacity(glyph_instances.len());
 
         // select glyphs that have not been requested yet.
         for glyph in glyph_instances {
-            let glyph_request = GlyphRequest::new(
-                font_key,
-                size,
-                color,
-                glyph.index,
-                glyph.point,
-                render_mode,
-                glyph_options,
-            );
+            let glyph_request = GlyphRequest::new(font.clone(),
+                                                  glyph.index,
+                                                  glyph.point);
 
             match glyph_cache.entry(glyph_request.clone(), current_frame_id) {
                 Entry::Occupied(entry) => {
                     if let &Some(texture_cache_item_id) = entry.get() {
                         requested_items.insert(texture_cache_item_id);
                     }
                 }
                 Entry::Vacant(..) => {
                     if !self.pending_glyphs.contains(&glyph_request) {
                         self.pending_glyphs.insert(glyph_request.clone());
-                        glyphs.push(glyph_request);
+                        glyphs.push(glyph_request.clone());
                     }
                 }
             }
         }
 
         if glyphs.is_empty() {
             return;
         }
@@ -196,38 +189,40 @@ impl GlyphRasterizer {
         // possible and in that task use rayon's fork join dispatch to rasterize the
         // glyphs in the thread pool.
         self.workers.spawn(move || {
             let jobs = glyphs.par_iter().map(|request: &GlyphRequest| {
                 profile_scope!("glyph-raster");
                 let mut context = font_contexts.lock_current_context();
                 let job = GlyphRasterJob {
                     request: request.clone(),
-                    result: context.rasterize_glyph(
-                        &request.key,
-                        request.render_mode,
-                        request.glyph_options
-                    ),
+                    result: context.rasterize_glyph(&request.font, &request.key),
                 };
 
                 // Sanity check.
                 if let Some(ref glyph) = job.result {
                     let bpp = 4; // We always render glyphs in 32 bits RGBA format.
                     assert_eq!(glyph.bytes.len(), bpp * (glyph.width * glyph.height) as usize);
                 }
 
                 job
             }).collect();
 
             glyph_tx.send(jobs).unwrap();
         });
     }
 
-    pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.font_contexts.lock_shared_context().get_glyph_dimensions(glyph_key)
+    pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
+                                glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
+        self.font_contexts.lock_shared_context().get_glyph_dimensions(font, glyph_key)
+    }
+
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        self.font_contexts.lock_shared_context().get_glyph_index(font_key, ch)
     }
 
     pub fn resolve_glyphs(
         &mut self,
         current_frame_id: FrameId,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
         requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
@@ -317,34 +312,27 @@ impl FontContext {
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 pub struct GlyphRequest {
     pub key: GlyphKey,
-    pub render_mode: FontRenderMode,
-    pub glyph_options: Option<GlyphOptions>,
+    pub font: FontInstanceKey,
 }
 
 impl GlyphRequest {
     pub fn new(
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
+        font: FontInstanceKey,
         index: u32,
-        point: LayoutPoint,
-        render_mode: FontRenderMode,
-        glyph_options: Option<GlyphOptions>,
-    ) -> Self {
+        point: LayoutPoint) -> Self {
         GlyphRequest {
-            key: GlyphKey::new(font_key, size, color, index, point, render_mode),
-            render_mode,
-            glyph_options,
+            key: GlyphKey::new(index, point, font.render_mode),
+            font,
         }
     }
 }
 
 struct GlyphRasterJob {
     request: GlyphRequest,
     result: Option<RasterizedGlyph>,
 }
@@ -362,39 +350,43 @@ fn raterize_200_glyphs() {
     let mut glyph_rasterizer = GlyphRasterizer::new(workers);
     let mut glyph_cache = GlyphCache::new();
     let mut requested_items = HashSet::default();
 
     let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
     font_file.read_to_end(&mut font_data).expect("failed to read font file");
 
-    let font_key = FontKey::new(0, 0);
+    let font_key = FontKey::new(IdNamespace(0), 0);
     glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
     let frame_id = FrameId(1);
 
     let mut glyph_instances = Vec::with_capacity(200);
     for i in 0..200 {
         glyph_instances.push(GlyphInstance {
             index: i, // It doesn't matter which glyphs we are actually rendering.
             point: LayoutPoint::new(0.0, 0.0),
         });
     }
 
+    let font = FontInstanceKey {
+        font_key,
+        color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
+        size: Au::from_px(32),
+        render_mode: FontRenderMode::Subpixel,
+        glyph_options: None,
+    };
+
     for i in 0..4 {
         glyph_rasterizer.request_glyphs(
             &mut glyph_cache,
             frame_id,
-            font_key,
-            Au::from_px(32),
-            ColorF::new(0.0, 0.0, 0.0, 1.0),
+            font.clone(),
             &glyph_instances[(50 * i)..(50 * (i + 1))],
-            FontRenderMode::Subpixel,
-            None,
             &mut requested_items,
         );
     }
 
     glyph_rasterizer.delete_font(font_key);
 
     glyph_rasterizer.resolve_glyphs(
         frame_id,
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,25 +1,24 @@
 /* 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/. */
 
-use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
-use api::{ClipId, ColorF, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
+use api::{ClipId, ColorU, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
 use api::{DevicePoint, ImageData, ImageFormat, PipelineId};
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
 // manages the allocation and freeing of these
@@ -41,19 +40,16 @@ pub enum SourceTexture {
     TextureCache(CacheTextureId),
     External(ExternalImageData),
     #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
     /// This is actually a gl::GLuint, with the shared texture id between the
     /// main context and the WebGL context.
     WebGL(u32),
 }
 
-const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
-pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;
-
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Debug, PartialEq, Eq)]
 pub enum TextureSampler {
     Color0,
     Color1,
     Color2,
@@ -124,75 +120,53 @@ pub enum ClipAttribute {
     // instance frequency
     RenderTaskIndex,
     LayerIndex,
     DataIndex,
     SegmentIndex,
     ResourceAddress,
 }
 
-// A packed RGBA8 color ordered for vertex data or similar.
-
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct PackedColor {
-    pub r: u8,
-    pub g: u8,
-    pub b: u8,
-    pub a: u8,
-}
-
-impl PackedColor {
-    pub fn from_color(color: &ColorF) -> PackedColor {
-        PackedColor {
-            r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
-        }
-    }
-}
-
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct DebugFontVertex {
     pub x: f32,
     pub y: f32,
-    pub color: PackedColor,
+    pub color: ColorU,
     pub u: f32,
     pub v: f32,
 }
 
 impl DebugFontVertex {
-    pub fn new(x: f32, y: f32, u: f32, v: f32, color: PackedColor) -> DebugFontVertex {
+    pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
         DebugFontVertex {
             x,
             y,
             color,
             u,
             v,
         }
     }
 }
 
 #[repr(C)]
 pub struct DebugColorVertex {
     pub x: f32,
     pub y: f32,
-    pub color: PackedColor,
+    pub color: ColorU,
 }
 
 impl DebugColorVertex {
-    pub fn new(x: f32, y: f32, color: PackedColor) -> DebugColorVertex {
+    pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
         DebugColorVertex {
             x,
             y,
             color,
         }
     }
 }
 
@@ -287,47 +261,26 @@ impl RendererFrame {
     }
 }
 
 pub enum ResultMsg {
     RefreshShader(PathBuf),
     NewFrame(RendererFrame, TextureUpdateList, BackendProfileCounters),
 }
 
-#[repr(u32)]
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum AxisDirection {
-    Horizontal,
-    Vertical,
-}
-
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
 pub struct StackingContextIndex(pub usize);
 
 #[derive(Clone, Copy, Debug)]
 pub struct UvRect {
     pub uv0: DevicePoint,
     pub uv1: DevicePoint,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum LowLevelFilterOp {
-    Blur(Au, AxisDirection),
-    Brightness(Au),
-    Contrast(Au),
-    Grayscale(Au),
-    /// Fixed-point in `ANGLE_FLOAT_TO_FIXED` units.
-    HueRotate(i32),
-    Invert(Au),
-    Opacity(Au),
-    Saturate(Au),
-    Sepia(Au),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum HardwareCompositeOp {
     PremultipliedAlpha,
 }
 
 impl HardwareCompositeOp {
     pub fn to_blend_mode(&self) -> BlendMode {
         match *self {
             HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -11,19 +11,20 @@ use core_graphics::data_provider::CGData
 use core_graphics::font::{CGFont, CGGlyph};
 use core_graphics::geometry::{CGPoint, CGSize, CGRect};
 use core_text::font::CTFont;
 use core_text::font_descriptor::kCTFontDefaultOrientation;
 use core_text;
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, GlyphOptions, SubpixelPoint};
-use api::NativeFontHandle;
+use api::{GlyphKey, SubpixelPoint};
+use api::{FontInstanceKey, NativeFontHandle};
 use gamma_lut::{GammaLut, Color as ColorLut};
+use std::ptr;
 
 pub struct FontContext {
     cg_fonts: HashMap<FontKey, CGFont>,
     ct_fonts: HashMap<(FontKey, Au), CTFont>,
     gamma_lut: GammaLut,
 }
 
 // core text is safe to use on multiple threads and non-shareable resources are
@@ -51,16 +52,17 @@ impl RasterizedGlyph {
 }
 
 struct GlyphMetrics {
     rasterized_left: i32,
     rasterized_descent: i32,
     rasterized_ascent: i32,
     rasterized_width: u32,
     rasterized_height: u32,
+    advance: f32,
 }
 
 // According to the Skia source code, there's no public API to
 // determine if subpixel AA is supported. So jrmuizel ported
 // this function from Skia which is used to check if a glyph
 // can be rendered with subpixel AA.
 fn supports_subpixel_aa() -> bool {
     let mut cg_context = CGContext::create_bitmap_context(None, 1, 1, 8, 4,
@@ -92,16 +94,17 @@ fn get_glyph_metrics(ct_font: &CTFont,
         // Instead we are better off returning zero-sized metrics because this special
         // case is handled by the callers of this method.
         return GlyphMetrics {
             rasterized_left: 0,
             rasterized_width: 0,
             rasterized_height: 0,
             rasterized_ascent: 0,
             rasterized_descent: 0,
+            advance: 0.0,
         };
     }
 
     let (x_offset, y_offset) = subpixel_point.to_f64();
 
     // First round out to pixel boundaries
     // CG Origin is bottom left
     let mut left = bounds.origin.x.floor() as i32;
@@ -120,22 +123,28 @@ fn get_glyph_metrics(ct_font: &CTFont,
     left -= 1;
     bottom -= 1;
     right += 1;
     top += 1;
 
     let width = right - left;
     let height = top - bottom;
 
+    let advance = ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation,
+                                                  &glyph,
+                                                  ptr::null_mut(),
+                                                  1);
+
     let metrics = GlyphMetrics {
         rasterized_left: left,
         rasterized_width: width as u32,
         rasterized_height: height as u32,
         rasterized_ascent: top,
         rasterized_descent: -bottom,
+        advance: advance as f32,
     };
 
     metrics
 }
 
 impl FontContext {
     pub fn new() -> FontContext {
         debug!("Test for subpixel AA support: {}", supports_subpixel_aa());
@@ -205,29 +214,49 @@ impl FontContext {
                         cg_font,
                         size.to_f64_px());
                 entry.insert(ct_font.clone());
                 Some(ct_font)
             }
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let character = ch as u16;
+        let mut glyph = 0;
+
+        self.get_ct_font(font_key, Au(16 * 60))
+            .and_then(|ref ct_font| {
+                let result = ct_font.get_glyphs_for_characters(&character,
+                                                               &mut glyph,
+                                                               1);
+
+                if result {
+                    Some(glyph as u32)
+                } else {
+                    None
+                }
+            })
+    }
+
     pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.get_ct_font(key.font_key, key.size).and_then(|ref ct_font| {
+        self.get_ct_font(font.font_key, font.size).and_then(|ref ct_font| {
             let glyph = key.index as CGGlyph;
             let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
             if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                 None
             } else {
                 Some(GlyphDimensions {
                     left: metrics.rasterized_left,
                     top: metrics.rasterized_ascent,
                     width: metrics.rasterized_width as u32,
                     height: metrics.rasterized_height as u32,
+                    advance: metrics.advance,
                 })
             }
         })
     }
 
     // Assumes the pixels here are linear values from CG
     fn gamma_correct_pixels(&self, pixels: &mut Vec<u8>, width: usize,
                             height: usize, render_mode: FontRenderMode,
@@ -263,33 +292,32 @@ impl FontContext {
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
             println!("");
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           _glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
-        let ct_font = match self.get_ct_font(key.font_key, key.size) {
+        let ct_font = match self.get_ct_font(font.font_key, font.size) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank())
         };
 
         let glyph = key.index as CGGlyph;
         let metrics = get_glyph_metrics(&ct_font, glyph, &key.subpixel_point);
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return Some(RasterizedGlyph::blank())
         }
 
-        let context_flags = match render_mode {
+        let context_flags = match font.render_mode {
             FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
             FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
         };
 
         let mut cg_context = CGContext::create_bitmap_context(None, metrics.rasterized_width as usize,
                                                               metrics.rasterized_height as usize,
                                                               8,
                                                               metrics.rasterized_width as usize * 4,
@@ -313,17 +341,17 @@ impl FontContext {
         // If we draw grayscale/mono on an opaque background
         // the RGB channels are the alpha values from transparent backgrounds
         // with the alpha set as opaque.
         // At the end of all this, WR expects individual RGB channels and ignores alpha
         // for subpixel AA.
         // For alpha/mono, WR ignores all channels other than alpha.
         // Also note that WR expects text to be black bg with white text, so invert
         // when we draw the glyphs.
-        let (antialias, smooth) = match render_mode {
+        let (antialias, smooth) = match font.render_mode {
             FontRenderMode::Subpixel => (true, true),
             FontRenderMode::Alpha => (true, false),
             FontRenderMode::Mono => (false, false),
         };
 
         // These are always true in Gecko, even for non-AA fonts
         cg_context.set_allows_font_subpixel_positioning(true);
         cg_context.set_should_subpixel_position_fonts(true);
@@ -364,49 +392,49 @@ impl FontContext {
         cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
         cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
         ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
 
         let mut rasterized_pixels = cg_context.data().to_vec();
 
         // Convert to linear space for subpixel AA.
         // We explicitly do not do this for grayscale AA
-        if render_mode == FontRenderMode::Subpixel {
+        if font.render_mode == FontRenderMode::Subpixel {
             self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
                                                                metrics.rasterized_width as usize,
                                                                metrics.rasterized_height as usize);
         }
 
         // We need to invert the pixels back since right now
         // transparent pixels are actually opaque white.
         for i in 0..metrics.rasterized_height {
             let current_height = (i * metrics.rasterized_width * 4) as usize;
             let end_row = current_height + (metrics.rasterized_width as usize * 4);
 
             for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
                 pixel[0] = 255 - pixel[0];
                 pixel[1] = 255 - pixel[1];
                 pixel[2] = 255 - pixel[2];
 
-                pixel[3] = match render_mode {
+                pixel[3] = match font.render_mode {
                     FontRenderMode::Subpixel => 255,
                     _ => {
                         assert_eq!(pixel[0], pixel[1]);
                         assert_eq!(pixel[0], pixel[2]);
                         pixel[0]
                     }
                 }; // end match
             } // end row
         } // end height
 
         self.gamma_correct_pixels(&mut rasterized_pixels,
                                   metrics.rasterized_width as usize,
                                   metrics.rasterized_height as usize,
-                                  render_mode,
-                                  key.color);
+                                  font.render_mode,
+                                  font.color);
 
         Some(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             bytes: rasterized_pixels,
         })
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -1,24 +1,24 @@
 /* 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/. */
 
 use app_units::Au;
-use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{NativeFontHandle, GlyphOptions};
+use api::{FontInstanceKey, FontKey, FontRenderMode, GlyphDimensions};
+use api::{NativeFontHandle};
 use api::{GlyphKey};
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
-use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
+use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32, FT_Get_Char_Index};
 
 use std::{cmp, mem, ptr, slice};
 use std::collections::HashMap;
 
 // This constant is not present in the freetype
 // bindings due to bindgen not handling the way
 // the macro is defined.
 const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
@@ -150,37 +150,56 @@ impl FontContext {
             let top = metrics.horiBearingY >> 6;
             let right = (metrics.horiBearingX + metrics.width + 0x3f) >> 6;
             let bottom = (metrics.horiBearingY + metrics.height + 0x3f) >> 6;
             Some(GlyphDimensions {
                 left: left as i32,
                 top: top as i32,
                 width: (right - left) as u32,
                 height: (bottom - top) as u32,
+                advance: metrics.horiAdvance as f32 / 64.0,
             })
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let face = self.faces
+                       .get(&font_key)
+                       .expect("Unknown font key!");
+        unsafe {
+            let idx = FT_Get_Char_Index(face.face, ch as _);
+            if idx != 0 {
+                Some(idx)
+            } else {
+                None
+            }
+        }
+    }
+
     pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.load_glyph(key.font_key, key.size, key.index)
+        self.load_glyph(font.font_key,
+                        font.size,
+                        key.index)
             .and_then(Self::get_glyph_dimensions_impl)
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           _glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
-        let slot = match self.load_glyph(key.font_key, key.size, key.index) {
+        let slot = match self.load_glyph(font.font_key,
+                                         font.size,
+                                         key.index) {
             Some(slot) => slot,
             None => return None,
         };
-        let render_mode = match render_mode {
+        let render_mode = match font.render_mode {
             FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
             FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
         };
 
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
         if result != SUCCESS {
             error!("Unable to rasterize {:?} with {:?}, {:?}", key, render_mode, result);
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 use std::collections::HashMap;
 use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, GlyphOptions};
+use api::{FontInstanceKey, GlyphKey, GlyphOptions};
 use gamma_lut::{GammaLut, Color as ColorLut};
 
 use dwrote;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
@@ -81,38 +81,16 @@ fn dwrite_render_mode(font_face: &dwrote
     if dwrite_render_mode  == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
         // Outline mode is not supported
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
     }
 
     dwrite_render_mode
 }
 
-fn get_glyph_dimensions_with_analysis(analysis: dwrote::GlyphRunAnalysis,
-                                      texture_type: dwrote::DWRITE_TEXTURE_TYPE)
-                                      -> Option<GlyphDimensions> {
-    let bounds = analysis.get_alpha_texture_bounds(texture_type);
-
-    let width = (bounds.right - bounds.left) as u32;
-    let height = (bounds.bottom - bounds.top) as u32;
-
-    // Alpha texture bounds can sometimes return an empty rect
-    // Such as for spaces
-    if width == 0 || height == 0 {
-        return None
-    }
-
-    Some(GlyphDimensions {
-        left: bounds.left,
-        top: -bounds.top,
-        width,
-        height,
-    })
-}
-
 impl FontContext {
     pub fn new() -> FontContext {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
         let contrast = 1.0;
         let gamma = 1.8;
         let gdi_gamma = 2.3;
@@ -170,65 +148,101 @@ impl FontContext {
                 let g = pixel[1];
                 let b = pixel[2];
                 print!("({}, {}, {}) ", r, g, b, );
             }
             println!("");
         }
     }
 
-    fn create_glyph_analysis(&self, key: &GlyphKey,
-                            render_mode: FontRenderMode,
-                            options: Option<GlyphOptions>) ->
+    fn create_glyph_analysis(&self,
+                             font: &FontInstanceKey,
+                             key: &GlyphKey) ->
                             dwrote::GlyphRunAnalysis {
-        let face = self.fonts.get(&key.font_key).unwrap();
+        let face = self.fonts.get(&font.font_key).unwrap();
         let glyph = key.index as u16;
         let advance = 0.0f32;
         let offset = dwrote::GlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0 };
 
         let glyph_run = dwrote::DWRITE_GLYPH_RUN {
             fontFace: unsafe { face.as_ptr() },
-            fontEmSize: key.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
+            fontEmSize: font.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
             glyphCount: 1,
             glyphIndices: &glyph,
             glyphAdvances: &advance,
             glyphOffsets: &offset,
             isSideways: 0,
             bidiLevel: 0,
         };
 
-        let dwrite_measure_mode = dwrite_measure_mode(render_mode, options);
+        let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
+                                                      font.glyph_options);
         let dwrite_render_mode = dwrite_render_mode(face,
-                                                    render_mode,
-                                                    key.size.to_f32_px(),
+                                                    font.render_mode,
+                                                    font.size.to_f32_px(),
                                                     dwrite_measure_mode,
-                                                    options);
+                                                    font.glyph_options);
 
         let (x_offset, y_offset) = key.subpixel_point.to_f64();
         let transform = Some(
                         dwrote::DWRITE_MATRIX { m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0,
                                                 dx: x_offset as f32, dy: y_offset as f32 }
                         );
 
         dwrote::GlyphRunAnalysis::create(&glyph_run, 1.0, transform,
                                          dwrite_render_mode,
                                          dwrite_measure_mode,
                                          0.0, 0.0)
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let face = self.fonts.get(&font_key).unwrap();
+        let indices = face.get_glyph_indices(&[ch as u32]);
+        indices.first().map(|idx| *idx as u32)
+    }
+
     // TODO: Pipe GlyphOptions into glyph_dimensions too
     pub fn get_glyph_dimensions(&self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey)
                                 -> Option<GlyphDimensions> {
         // Probably have to default to something else here.
         let render_mode = FontRenderMode::Subpixel;
-        let analysis = self.create_glyph_analysis(key, render_mode, None);
+        let analysis = self.create_glyph_analysis(font, key);
 
         let texture_type = dwrite_texture_type(render_mode);
-        get_glyph_dimensions_with_analysis(analysis, texture_type)
+
+        let bounds = analysis.get_alpha_texture_bounds(texture_type);
+
+        let width = (bounds.right - bounds.left) as u32;
+        let height = (bounds.bottom - bounds.top) as u32;
+
+        // Alpha texture bounds can sometimes return an empty rect
+        // Such as for spaces
+        if width == 0 || height == 0 {
+            return None
+        }
+
+        let face = self.fonts.get(&font.font_key).unwrap();
+        face.get_design_glyph_metrics(&[key.index as u16], false)
+            .first()
+            .map(|metrics| {
+                let em_size = font.size.to_f32_px() / 16.;
+                let design_units_per_pixel = face.metrics().designUnitsPerEm as f32 / 16. as f32;
+                let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
+                let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels;
+
+                GlyphDimensions {
+                    left: bounds.left,
+                    top: -bounds.top,
+                    width,
+                    height,
+                    advance: advance,
+                }
+            })
     }
 
     // DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
     // TODO: Decide whether all fonts should return RGB or BGR
     fn convert_to_rgba(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
             FontRenderMode::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
@@ -263,57 +277,54 @@ impl FontContext {
                     rgba_pixels[i*4+3] = 0xff;
                 }
                 rgba_pixels
             }
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
-        let analysis = self.create_glyph_analysis(key,
-                                                  render_mode,
-                                                  glyph_options);
-        let texture_type = dwrite_texture_type(render_mode);
+        let analysis = self.create_glyph_analysis(font, key);
+        let texture_type = dwrite_texture_type(font.render_mode);
 
         let bounds = analysis.get_alpha_texture_bounds(texture_type);
         let width = (bounds.right - bounds.left) as usize;
         let height = (bounds.bottom - bounds.top) as usize;
 
         // Alpha texture bounds can sometimes return an empty rect
         // Such as for spaces
         if width == 0 || height == 0 {
             return None;
         }
 
         let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
 
-        if render_mode != FontRenderMode::Mono {
-            let lut_correction = match glyph_options {
+        if font.render_mode != FontRenderMode::Mono {
+            let lut_correction = match font.glyph_options {
                 Some(option) => {
                     if option.force_gdi_rendering {
                         &self.gdi_gamma_lut
                     } else {
                         &self.gamma_lut
                     }
                 },
                 None => &self.gamma_lut
             };
 
             lut_correction.preblend_rgb(&mut pixels, width, height,
-                                        ColorLut::new(key.color.r,
-                                                      key.color.g,
-                                                      key.color.b,
-                                                      key.color.a));
+                                        ColorLut::new(font.color.r,
+                                                      font.color.g,
+                                                      font.color.b,
+                                                      font.color.a));
         }
 
-        let rgba_pixels = self.convert_to_rgba(&mut pixels, render_mode);
+        let rgba_pixels = self.convert_to_rgba(&mut pixels, font.render_mode);
 
         Some(RasterizedGlyph {
             left: bounds.left as f32,
             top: -bounds.top as f32,
             width: width as u32,
             height: height as u32,
             bytes: rgba_pixels,
         })
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,27 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
-use api::{device_length, LayerVector2D};
+use api::{device_length, FontInstanceKey, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
-use util::{TransformedRect, recycle_vec};
+use util::{pack_as_float, TransformedRect, recycle_vec};
 
 
 pub const CLIP_DATA_GPU_BLOCKS: usize = 10;
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
@@ -111,16 +111,17 @@ pub enum PrimitiveKind {
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     TextShadow,
+    Line,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
@@ -175,16 +176,34 @@ pub struct RectanglePrimitive {
 }
 
 impl ToGpuBlocks for RectanglePrimitive {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.color);
     }
 }
 
+#[derive(Debug, Clone)]
+#[repr(C)]
+pub struct LinePrimitive {
+    pub color: ColorF,
+    pub style: LineStyle,
+    pub orientation: LineOrientation,
+}
+
+impl ToGpuBlocks for LinePrimitive {
+    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
+        request.push(self.color);
+        request.push([pack_as_float(self.style as u32),
+                      pack_as_float(self.orientation as u32),
+                      0.0,
+                      0.0]);
+    }
+}
+
 #[derive(Debug)]
 pub enum ImagePrimitiveKind {
     Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
     WebGL(WebGLContextId),
 }
 
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
@@ -534,22 +553,23 @@ impl TextRunPrimitiveCpu {
         }
 
         let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
         let render_mode = match run_mode {
             TextRunMode::Normal => self.normal_render_mode,
             TextRunMode::Shadow => self.shadow_render_mode,
         };
 
-        resource_cache.request_glyphs(self.font_key,
-                                      font_size_dp,
-                                      self.color,
-                                      &self.glyph_instances,
-                                      render_mode,
-                                      self.glyph_options);
+        let font = FontInstanceKey::new(self.font_key,
+                                        font_size_dp,
+                                        self.color,
+                                        render_mode,
+                                        self.glyph_options);
+
+        resource_cache.request_glyphs(font, &self.glyph_instances);
     }
 
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
         request.push([self.offset.x, self.offset.y, 0.0, 0.0]);
 
         // Two glyphs are packed per GPU block.
@@ -730,63 +750,67 @@ pub enum PrimitiveContainer {
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     RadialGradient(RadialGradientPrimitiveCpu),
     BoxShadow(BoxShadowPrimitiveCpu),
     TextShadow(TextShadowPrimitiveCpu),
+    Line(LinePrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
     pub cpu_rectangles: Vec<RectanglePrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_text_shadows: Vec<TextShadowPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
     pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
+    pub cpu_lines: Vec<LinePrimitive>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_rectangles: Vec::new(),
             cpu_bounding_rects: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_text_shadows: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
             cpu_box_shadows: Vec::new(),
+            cpu_lines: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_rectangles: recycle_vec(self.cpu_rectangles),
             cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_text_shadows: recycle_vec(self.cpu_text_shadows),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
+            cpu_lines: recycle_vec(self.cpu_lines),
         }
     }
 
     pub fn add_primitive(&mut self,
                          local_rect: &LayerRect,
                          local_clip_rect: &LayerRect,
                          clips: Vec<ClipSource>,
                          clip_info: Option<MaskCacheInfo>,
@@ -808,16 +832,33 @@ impl PrimitiveStore {
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
+            PrimitiveContainer::Line(line) => {
+                let metadata = PrimitiveMetadata {
+                    opacity: PrimitiveOpacity::translucent(),
+                    clips,
+                    clip_cache_info: clip_info,
+                    prim_kind: PrimitiveKind::Line,
+                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
+                    gpu_location: GpuCacheHandle::new(),
+                    render_task: None,
+                    clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
+                };
+
+                self.cpu_lines.push(line);
+                metadata
+            }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_location: GpuCacheHandle::new(),
@@ -1074,17 +1115,18 @@ impl PrimitiveStore {
                 if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
                     resource_cache.request_image(mask.image, ImageRendering::Auto, None);
                 }
             }
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
-            PrimitiveKind::Border  => {}
+            PrimitiveKind::Border |
+            PrimitiveKind::Line => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
                 // Here, we calculate the size of the patch required in order
                 // to create the box shadow corner. First, scale it by the
                 // device pixel ratio since the cache shader expects vertices
                 // in device space. The shader adds a 1-pixel border around
                 // the patch, in order to prevent bilinear filter artifacts as
                 // the patch is clamped / mirrored across the box shadow rect.
@@ -1158,16 +1200,20 @@ impl PrimitiveStore {
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
                 PrimitiveKind::Rectangle => {
                     let rect = &self.cpu_rectangles[cpu_prim_index.0];
                     rect.write_gpu_blocks(request);
                 }
+                PrimitiveKind::Line => {
+                    let line = &self.cpu_lines[metadata.cpu_prim_index.0];
+                    line.write_gpu_blocks(request);
+                }
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
                 PrimitiveKind::BoxShadow => {
                     let box_shadow = &self.cpu_box_shadows[cpu_prim_index.0];
                     box_shadow.write_gpu_blocks(request);
                 }
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use debug_render::DebugRenderer;
 use device::{Device, GpuMarker, GpuSample, NamedTag};
 use euclid::{Point2D, Size2D, Rect, vec2};
 use std::collections::vec_deque::VecDeque;
 use std::f32;
 use std::mem;
-use api::ColorF;
+use api::{ColorF, ColorU};
 use time::precise_time_ns;
 
 const GRAPH_WIDTH: f32 = 1024.0;
 const GRAPH_HEIGHT: f32 = 320.0;
 const GRAPH_PADDING: f32 = 8.0;
 const GRAPH_FRAME_HEIGHT: f32 = 16.0;
 const PROFILE_PADDING: f32 = 10.0;
 
@@ -324,23 +324,30 @@ pub struct IpcProfileCounters {
     pub build_time: TimeProfileCounter,
     pub consume_time: TimeProfileCounter,
     pub send_time: TimeProfileCounter,
     pub total_time: TimeProfileCounter,
     pub display_lists: ResourceProfileCounter,
 }
 
 impl IpcProfileCounters {
-    pub fn set(&mut self, build_start: u64, build_end: u64, 
-                              consume_start: u64, consume_end: u64,
-                              display_len: usize) {
-        self.build_time.inc(build_end - build_start);
-        self.consume_time.inc(consume_end - consume_start);
-        self.send_time.inc(consume_start - build_end);
-        self.total_time.inc(consume_end - build_start);
+    pub fn set(&mut self,
+               build_start: u64,
+               build_end: u64,
+               send_start: u64,
+               consume_start: u64,
+               consume_end: u64,
+               display_len: usize) {
+        let build_time = build_end - build_start;
+        let consume_time = consume_end - consume_start;
+        let send_time = consume_start - send_start;
+        self.build_time.inc(build_time);
+        self.consume_time.inc(consume_time);
+        self.send_time.inc(send_time);
+        self.total_time.inc(build_time + consume_time + send_time);
         self.display_lists.inc(display_len);
     }
 }
 
 impl BackendProfileCounters {
     pub fn new() -> BackendProfileCounters {
         BackendProfileCounters {
             total_time: TimeProfileCounter::new("Backend CPU Time", false),
@@ -463,74 +470,74 @@ impl ProfileGraph {
                   y: f32,
                   description: &'static str,
                   debug_renderer: &mut DebugRenderer) -> Rect<f32> {
         let size = Size2D::new(600.0, 120.0);
         let line_height = debug_renderer.line_height();
         let mut rect = Rect::new(Point2D::new(x, y), size);
         let stats = self.stats();
 
-        let text_color = ColorF::new(1.0, 1.0, 0.0, 1.0);
+        let text_color = ColorU::new(255, 255, 0, 255);
         let text_origin = rect.origin + vec2(rect.size.width, 20.0);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y,
                                 description,
-                                &ColorF::new(0.0, 1.0, 0.0, 1.0));
+                                ColorU::new(0, 255, 0, 255));
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height,
                                 &format!("Min: {:.2} ms", stats.min_value),
-                                &text_color);
+                                text_color);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height * 2.0,
                                 &format!("Mean: {:.2} ms", stats.mean_value),
-                                &text_color);
+                                text_color);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height * 3.0,
                                 &format!("Max: {:.2} ms", stats.max_value),
-                                &text_color);
+                                text_color);
 
         rect.size.width += 140.0;
         debug_renderer.add_quad(rect.origin.x,
                                 rect.origin.y,
                                 rect.origin.x + rect.size.width + 10.0,
                                 rect.origin.y + rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
 
         let bx0 = x + 10.0;
         let by0 = y + 10.0;
         let bx1 = bx0 + size.width - 20.0;
         let by1 = by0 + size.height - 20.0;
 
         let w = (bx1 - bx0) / self.max_samples as f32;
         let h = by1 - by0;
 
-        let color_t0 = ColorF::new(0.0, 1.0, 0.0, 1.0);
-        let color_b0 = ColorF::new(0.0, 0.7, 0.0, 1.0);
+        let color_t0 = ColorU::new(0, 255, 0, 255);
+        let color_b0 = ColorU::new(0, 180, 0, 255);
 
-        let color_t1 = ColorF::new(0.0, 1.0, 0.0, 1.0);
-        let color_b1 = ColorF::new(0.0, 0.7, 0.0, 1.0);
+        let color_t1 = ColorU::new(0, 255, 0, 255);
+        let color_b1 = ColorU::new(0, 180, 0, 255);
 
-        let color_t2 = ColorF::new(1.0, 0.0, 0.0, 1.0);
-        let color_b2 = ColorF::new(0.7, 0.0, 0.0, 1.0);
+        let color_t2 = ColorU::new(255, 0, 0, 255);
+        let color_b2 = ColorU::new(180, 0, 0, 255);
 
         for (index, sample) in self.values.iter().enumerate() {
             let sample = *sample;
             let x1 = bx1 - index as f32 * w;
             let x0 = x1 - w;
 
             let y0 = by1 - (sample / stats.max_value) as f32 * h;
             let y1 = by1;
 
             let (color_top, color_bottom) = if sample < 1000.0 / 60.0 {
-                (&color_t0, &color_b0)
+                (color_t0, color_b0)
             } else if sample < 1000.0 / 30.0 {
-                (&color_t1, &color_b1)
+                (color_t1, color_b1)
             } else {
-                (&color_t2, &color_b2)
+                (color_t2, color_b2)
             };
 
             debug_renderer.add_quad(x0, y0, x1, y1, color_top, color_bottom);
         }
 
         rect
     }
 }
@@ -571,18 +578,18 @@ impl GpuFrameCollection {
                                       Size2D::new(GRAPH_WIDTH + 2.0 * GRAPH_PADDING,
                                                   GRAPH_HEIGHT + 2.0 * GRAPH_PADDING));
         let graph_rect = bounding_rect.inflate(-GRAPH_PADDING, -GRAPH_PADDING);
 
         debug_renderer.add_quad(bounding_rect.origin.x,
                                 bounding_rect.origin.y,
                                 bounding_rect.origin.x + bounding_rect.size.width,
                                 bounding_rect.origin.y + bounding_rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
 
         let w = graph_rect.size.width;
         let mut y0 = graph_rect.origin.y;
 
         let max_time = self.frames
                            .iter()
                            .max_by_key(|f| f.total_time)
                            .unwrap()
@@ -599,18 +606,18 @@ impl GpuFrameCollection {
 
                 let mut bottom_color = sample.tag.color;
                 bottom_color.a *= 0.5;
 
                 debug_renderer.add_quad(x0,
                                         y0,
                                         x1,
                                         y1,
-                                        &sample.tag.color,
-                                        &bottom_color);
+                                        sample.tag.color.into(),
+                                        bottom_color.into());
             }
 
             y0 = y1;
         }
 
         bounding_rect
     }
 }
@@ -652,25 +659,25 @@ impl Profiler {
             (self.x_left, self.y_left)
         } else {
             (self.x_right, self.y_right)
         };
         let mut color_index = 0;
         let line_height = debug_renderer.line_height();
 
         let colors = [
-            ColorF::new(1.0, 1.0, 1.0, 1.0),
-            ColorF::new(1.0, 1.0, 0.0, 1.0),
+            ColorU::new(255, 255, 255, 255),
+            ColorU::new(255, 255, 0, 255),
         ];
 
         for counter in counters {
             let rect = debug_renderer.add_text(current_x,
                                                current_y,
                                                counter.description(),
-                                               &colors[color_index]);
+                                               colors[color_index]);
             color_index = (color_index+1) % colors.len();
 
             label_rect = label_rect.union(&rect);
             current_y += line_height;
         }
 
         color_index = 0;
         current_x = label_rect.origin.x + label_rect.size.width + 60.0;
@@ -679,30 +686,30 @@ impl Profiler {
         } else {
             self.y_right
         };
 
         for counter in counters {
             let rect = debug_renderer.add_text(current_x,
                                                     current_y,
                                                     &counter.value(),
-                                                    &colors[color_index]);
+                                                    colors[color_index]);
             color_index = (color_index+1) % colors.len();
 
             value_rect = value_rect.union(&rect);
             current_y += line_height;
         }
 
         let total_rect = label_rect.union(&value_rect).inflate(10.0, 10.0);
         debug_renderer.add_quad(total_rect.origin.x,
                                 total_rect.origin.y,
                                 total_rect.origin.x + total_rect.size.width,
                                 total_rect.origin.y + total_rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
         let new_y = total_rect.origin.y + total_rect.size.height + 30.0;
         if left {
             self.y_left = new_y;
         } else {
             self.y_right = new_y;
         }
     }
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -63,16 +63,18 @@ pub struct RenderBackend {
     current_bound_webgl_context_id: Option<WebGLContextId>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
     next_webgl_id: usize,
 
     vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
 
+    enable_render_on_scroll: bool,
+
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: bool,
 }
 
@@ -86,17 +88,18 @@ impl RenderBackend {
                workers: Arc<ThreadPool>,
                notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
                webrender_context_handle: Option<GLContextHandleWrapper>,
                config: FrameBuilderConfig,
                recorder: Option<Box<ApiRecordingReceiver>>,
                main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>,
                vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
-               initial_window_size: DeviceUintSize) -> RenderBackend {
+               initial_window_size: DeviceUintSize,
+               enable_render_on_scroll: bool) -> RenderBackend {
 
         let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx,
             payload_rx,
@@ -116,16 +119,17 @@ impl RenderBackend {
             webgl_contexts: HashMap::new(),
             current_bound_webgl_context_id: None,
             recorder,
             main_thread_dispatcher,
             next_webgl_id: 0,
             vr_compositor_handler,
             window_size: initial_window_size,
             inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
+            enable_render_on_scroll,
             render_on_scroll: false,
         }
     }
 
     fn scroll_frame(&mut self, frame_maybe: Option<RendererFrame>,
                     profile_counters: &mut BackendProfileCounters) {
         match frame_maybe {
             Some(frame) => {
@@ -155,24 +159,32 @@ impl RenderBackend {
                         }
                         ApiMsg::AddNativeFont(id, native_font_handle) => {
                             self.resource_cache
                                 .add_font_template(id, FontTemplate::Native(native_font_handle));
                         }
                         ApiMsg::DeleteFont(id) => {
                             self.resource_cache.delete_font_template(id);
                         }
-                        ApiMsg::GetGlyphDimensions(glyph_keys, tx) => {
+                        ApiMsg::GetGlyphDimensions(font, glyph_keys, tx) => {
                             let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
                             for glyph_key in &glyph_keys {
-                                let glyph_dim = self.resource_cache.get_glyph_dimensions(glyph_key);
+                                let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
                                 glyph_dimensions.push(glyph_dim);
                             };
                             tx.send(glyph_dimensions).unwrap();
                         }
+                        ApiMsg::GetGlyphIndices(font_key, text, tx) => {
+                            let mut glyph_indices = Vec::new();
+                            for ch in text.chars() {
+                                let index = self.resource_cache.get_glyph_index(font_key, ch);
+                                glyph_indices.push(index);
+                            };
+                            tx.send(glyph_indices).unwrap();
+                        }
                         ApiMsg::AddImage(id, descriptor, data, tiling) => {
                             if let ImageData::Raw(ref bytes) = data {
                                 profile_counters.resources.image_templates.inc(bytes.len());
                             }
                             self.resource_cache.add_image_template(id, descriptor, data, tiling);
                         }
                         ApiMsg::UpdateImage(id, descriptor, bytes, dirty_rect) => {
                             self.resource_cache.update_image_template(id, descriptor, bytes, dirty_rect);
@@ -232,17 +244,17 @@ impl RenderBackend {
                                 BuiltDisplayList::from_data(data.display_list_data,
                                                             display_list_descriptor);
 
                             if !preserve_frame_state {
                                 self.discard_frame_state_for_pipeline(pipeline_id);
                             }
 
                             let display_list_len = built_display_list.data().len();
-                            let (builder_start_time, builder_finish_time) = built_display_list.times();
+                            let (builder_start_time, builder_finish_time, send_start_time) = built_display_list.times();
 
                             let display_list_received_time = precise_time_ns();
 
                             profile_counters.total_time.profile(|| {
                                 self.scene.set_display_list(pipeline_id,
                                                             epoch,
                                                             built_display_list,
                                                             background_color,
@@ -253,18 +265,21 @@ impl RenderBackend {
 
                             self.render_on_scroll = false; //wait for `GenerateFrame`
 
                             // Note: this isn't quite right as auxiliary values will be
                             // pulled out somewhere in the prim_store, but aux values are
                             // really simple and cheap to access, so it's not a big deal.
                             let display_list_consumed_time = precise_time_ns();
 
-                            profile_counters.ipc.set(builder_start_time, builder_finish_time,
-                                                     display_list_received_time, display_list_consumed_time,
+                            profile_counters.ipc.set(builder_start_time,
+                                                     builder_finish_time,
+                                                     send_start_time,
+                                                     display_list_received_time,
+                                                     display_list_consumed_time,
                                                      display_list_len);
                         }
                         ApiMsg::SetRootPipeline(pipeline_id) => {
                             profile_scope!("SetRootPipeline");
                             self.scene.set_root_pipeline_id(pipeline_id);
 
                             if self.scene.display_lists.get(&pipeline_id).is_none() {
                                 continue;
@@ -420,17 +435,17 @@ impl RenderBackend {
                             //           rebuild of the frame!
                             if let Some(property_bindings) = property_bindings {
                                 self.scene.properties.set_properties(property_bindings);
                                 profile_counters.total_time.profile(|| {
                                     self.build_scene();
                                 });
                             }
 
-                            self.render_on_scroll = true;
+                            self.render_on_scroll = self.enable_render_on_scroll;
 
                             let frame = {
                                 let counters = &mut profile_counters.resources.texture_cache;
                                 let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
                                 profile_counters.total_time.profile(|| {
                                     self.render(counters, gpu_cache_counters)
                                 })
                             };
@@ -441,16 +456,19 @@ impl RenderBackend {
                         }
                         ApiMsg::ExternalEvent(evt) => {
                             let notifier = self.notifier.lock();
                             notifier.unwrap()
                                     .as_mut()
                                     .unwrap()
                                     .external_event(evt);
                         }
+                        ApiMsg::ClearNamespace(namespace) => {
+                            self.resource_cache.clear_namespace(namespace);
+                        }
                         ApiMsg::ShutDown => {
                             let notifier = self.notifier.lock();
                             notifier.unwrap()
                                     .as_mut()
                                     .unwrap()
                                     .shut_down();
                             break;
                         }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,21 +1,21 @@
 /* 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/. */
 
 use gpu_cache::GpuCacheHandle;
-use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
+use internal_types::HardwareCompositeOp;
 use mask_cache::MaskCacheInfo;
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use api::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{MixBlendMode};
+use api::{FilterOp, MixBlendMode};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum RenderTaskKey {
@@ -48,17 +48,17 @@ pub enum RenderTaskId {
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug, Clone)]
 pub enum AlphaRenderItem {
     Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
-    Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
+    Blend(StackingContextIndex, RenderTaskId, FilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
     SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -58,19 +58,21 @@ use api::{YuvColorSpace, YuvFormat};
 use api::{YUV_COLOR_SPACES, YUV_FORMATS};
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
 const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "C_TextRun", color: debug_colors::MISTYROSE };
+const GPU_TAG_CACHE_LINE: GpuProfileTag = GpuProfileTag { label: "C_Line", color: debug_colors::BROWN };
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "target", color: debug_colors::SLATEGREY };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { label: "data init", color: debug_colors::LIGHTGREY };
 const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { label: "Rect", color: debug_colors::RED };
+const GPU_TAG_PRIM_LINE: GpuProfileTag = GpuProfileTag { label: "Line", color: debug_colors::DARKRED };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { label: "Image", color: debug_colors::GREEN };
 const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag { label: "YuvImage", color: debug_colors::DARKGREEN };
 const GPU_TAG_PRIM_BLEND: GpuProfileTag = GpuProfileTag { label: "Blend", color: debug_colors::LIGHTBLUE };
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "SplitComposite", color: debug_colors::DARKBLUE };
 const GPU_TAG_PRIM_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "Composite", color: debug_colors::MAGENTA };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "TextRun", color: debug_colors::BLUE };
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag { label: "Gradient", color: debug_colors::YELLOW };
@@ -158,26 +160,29 @@ impl GpuProfile {
             paint_time_ns,
         }
     }
 }
 
 #[derive(Debug)]
 pub struct CpuProfile {
     pub frame_id: FrameId,
+    pub backend_time_ns: u64,
     pub composite_time_ns: u64,
     pub draw_calls: usize,
 }
 
 impl CpuProfile {
     fn new(frame_id: FrameId,
+           backend_time_ns: u64,
            composite_time_ns: u64,
            draw_calls: usize) -> CpuProfile {
         CpuProfile {
             frame_id,
+            backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BlendMode {
@@ -618,17 +623,19 @@ pub struct Renderer {
     pending_shader_updates: Vec<PathBuf>,
     current_frame: Option<RendererFrame>,
 
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     cs_box_shadow: LazilyCompiledShader,
     cs_text_run: LazilyCompiledShader,
+    cs_line: LazilyCompiledShader,
     cs_blur: LazilyCompiledShader,
+
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
     cs_clip_border: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
@@ -646,16 +653,17 @@ pub struct Renderer {
     ps_yuv_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
+    ps_line: PrimitiveShader,
 
     ps_blend: LazilyCompiledShader,
     ps_hw_composite: LazilyCompiledShader,
     ps_split_composite: LazilyCompiledShader,
     ps_composite: LazilyCompiledShader,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
 
@@ -791,16 +799,24 @@ impl Renderer {
         let cs_text_run = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
                                       "cs_text_run",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let cs_line = try!{
+            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
+                                      "ps_line",
+                                      &["CACHE"],
+                                      &mut device,
+                                      options.precache_shaders)
+        };
+
         let cs_blur = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Blur),
                                      "cs_blur",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
@@ -837,16 +853,23 @@ impl Renderer {
 
         let ps_rectangle_clip = try!{
             PrimitiveShader::new("ps_rectangle",
                                  &mut device,
                                  &[ CLIP_FEATURE ],
                                  options.precache_shaders)
         };
 
+        let ps_line = try!{
+            PrimitiveShader::new("ps_line",
+                                 &mut device,
+                                 &[],
+                                 options.precache_shaders)
+        };
+
         let ps_text_run = try!{
             PrimitiveShader::new("ps_text_run",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_text_run_subpixel = try!{
@@ -1132,16 +1155,17 @@ impl Renderer {
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
         let worker_config = ThreadPoolConfig::new()
             .thread_name(|idx|{ format!("WebRender:Worker#{}", idx) })
             .start_handler(|idx| { register_thread_with_profiler(format!("WebRender:Worker#{}", idx)); });
         let workers = options.workers.take().unwrap_or_else(||{
             Arc::new(ThreadPool::new(worker_config).unwrap())
         });
+        let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         try!{ thread::Builder::new().name("RenderBackend".to_string()).spawn(move || {
             let mut backend = RenderBackend::new(api_rx,
                                                  payload_rx,
                                                  payload_tx_for_backend,
                                                  result_tx,
                                                  device_pixel_ratio,
@@ -1149,33 +1173,35 @@ impl Renderer {
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
                                                  blob_image_renderer,
                                                  backend_vr_compositor,
-                                                 initial_window_size);
+                                                 initial_window_size,
+                                                 enable_render_on_scroll);
             backend.run(backend_profile_counters);
         })};
 
         let gpu_cache_texture = CacheTexture::new(&mut device);
 
         let gpu_profile = GpuProfiler::new(device.rc_gl());
 
         let renderer = Renderer {
             result_rx,
             device,
             current_frame: None,
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_box_shadow,
             cs_text_run,
+            cs_line,
             cs_blur,
             cs_clip_rectangle,
             cs_clip_border,
             cs_clip_image,
             ps_rectangle,
             ps_rectangle_clip,
             ps_text_run,
             ps_text_run_subpixel,
@@ -1187,16 +1213,17 @@ impl Renderer {
             ps_gradient,
             ps_angle_gradient,
             ps_radial_gradient,
             ps_cache_image,
             ps_blend,
             ps_hw_composite,
             ps_split_composite,
             ps_composite,
+            ps_line,
             notifier,
             debug: debug_renderer,
             render_target_debug,
             enable_batcher: options.enable_batcher,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             enable_profiler: options.enable_profiler,
@@ -1404,16 +1431,17 @@ impl Renderer {
                 let ns = current_time - self.last_time;
                 self.profile_counters.frame_time.set(ns);
 
                 if self.max_recorded_profiles > 0 {
                     while self.cpu_profiles.len() >= self.max_recorded_profiles {
                         self.cpu_profiles.pop_front();
                     }
                     let cpu_profile = CpuProfile::new(cpu_frame_id,
+                                                      self.backend_profile_counters.total_time.get(),
                                                       profile_timers.cpu_time.get(),
                                                       self.profile_counters.draw_calls.get());
                     self.cpu_profiles.push_back(cpu_profile);
                 }
 
                 if self.enable_profiler {
                     self.profiler.draw_profile(&mut self.device,
                                                &frame.profile_counters,
@@ -1659,16 +1687,20 @@ impl Renderer {
             AlphaBatchKind::Rectangle => {
                 let shader = if needs_clipping {
                     self.ps_rectangle_clip.get(&mut self.device, transform_kind)
                 } else {
                     self.ps_rectangle.get(&mut self.device, transform_kind)
                 };
                 (GPU_TAG_PRIM_RECT, shader)
             }
+            AlphaBatchKind::Line => {
+                let shader = self.ps_line.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_LINE, shader)
+            }
             AlphaBatchKind::TextRun => {
                 let shader = match batch.key.blend_mode {
                     BlendMode::Subpixel(..) => self.ps_text_run_subpixel.get(&mut self.device, transform_kind),
                     BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::None => self.ps_text_run.get(&mut self.device, transform_kind),
                 };
                 (GPU_TAG_PRIM_TEXT_RUN, shader)
             }
             AlphaBatchKind::Image(image_buffer_kind) => {
@@ -1873,16 +1905,32 @@ impl Renderer {
             let shader = self.cs_text_run.get(&mut self.device).unwrap();
 
             self.draw_instanced_batch(&target.text_run_cache_prims,
                                       vao,
                                       shader,
                                       &target.text_run_textures,
                                       &projection);
         }
+        if !target.line_cache_prims.is_empty() {
+            // TODO(gw): Technically, we don't need blend for solid
+            //           lines. We could check that here?
+            self.device.set_blend(true);
+            self.device.set_blend_mode_alpha();
+
+            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
+            let vao = self.prim_vao_id;
+            let shader = self.cs_line.get(&mut self.device).unwrap();
+
+            self.draw_instanced_batch(&target.line_cache_prims,
+                                      vao,
+                                      shader,
+                                      &BatchTextures::no_texture(),
+                                      &projection);
+        }
 
         if !target.alpha_batcher.is_empty() {
             let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
             self.device.set_blend(false);
             let mut prev_blend_mode = BlendMode::None;
 
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
@@ -2387,16 +2435,17 @@ pub struct RendererOptions {
     pub enable_clear_scissor: bool,
     pub enable_batcher: bool,
     pub render_target_debug: bool,
     pub max_texture_size: Option<u32>,
     pub cache_expiry_frames: u32,
     pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
+    pub enable_render_on_scroll: bool,
 }
 
 impl Default for RendererOptions {
     fn default() -> RendererOptions {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -2413,11 +2462,12 @@ impl Default for RendererOptions {
             enable_clear_scissor: true,
             enable_batcher: true,
             render_target_debug: false,
             max_texture_size: None,
             cache_expiry_frames: 600, // roughly, 10 seconds
             workers: None,
             blob_image_renderer: None,
             recorder: None,
+            enable_render_on_scroll: true,
         }
     }
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,39 +1,37 @@
 /* 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/. */
 
-use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use internal_types::{SourceTexture, TextureUpdateList};
 use profiler::TextureCacheProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheItemId};
-use api::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
-use api::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
-use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
-use api::{GlyphOptions, GlyphInstance, SubpixelPoint, TileOffset, TileSize};
+use api::{Epoch, FontInstanceKey, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
+use api::{ImageData, GlyphDimensions, WebGLContextId, IdNamespace};
+use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor};
+use api::{GlyphInstance, SubpixelPoint, TileOffset, TileSize};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
 use api::BlobImageResources;
 use api::{ExternalImageData, ExternalImageType, LayoutPoint};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
-const BLACK: ColorF = ColorF { r: 0.0, b: 0.0, g: 0.0, a: 1.0 };
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
 // values in the vertex shader. The reason
 // for this is that the texture may change
 // dimensions (e.g. the pages in a texture
 // atlas can grow). When this happens, by
 // storing the coordinates as texel values
@@ -133,33 +131,52 @@ impl<K,V> ResourceClassCache<K,V> where 
         self.resources.entry(key)
     }
 
     pub fn mark_as_needed(&mut self, key: &K, frame: FrameId) {
         self.last_access_times.insert((*key).clone(), frame);
     }
 
     fn expire_old_resources(&mut self, texture_cache: &mut TextureCache, frame_id: FrameId) {
-        let mut resources_to_destroy = vec![];
-        for (key, this_frame_id) in &self.last_access_times {
-            if *this_frame_id < frame_id {
-                resources_to_destroy.push((*key).clone())
-            }
-        }
+        //TODO: use retain when available
+        let resources_to_destroy = self.last_access_times.iter()
+            .filter_map(|(key, this_frame_id)| {
+                if *this_frame_id < frame_id {
+                    Some(key.clone())
+                } else {
+                    None
+                }
+            }).collect::<Vec<_>>();
+
         for key in resources_to_destroy {
             let resource =
                 self.resources
                     .remove(&key)
                     .expect("Resource was in `last_access_times` but not in `resources`!");
             self.last_access_times.remove(&key);
             if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
                 texture_cache.free(texture_cache_item_id)
             }
         }
     }
+
+    fn clear_keys<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
+    where for<'r> F: Fn(&'r &K) -> bool
+    {
+        let resources_to_destroy = self.resources.keys()
+            .filter(&key_fun)
+            .cloned()
+            .collect::<Vec<_>>();
+        for key in resources_to_destroy {
+            let resource = self.resources.remove(&key).unwrap();
+            if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
+                texture_cache.free(texture_cache_item_id)
+            }
+        }
+    }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 struct ImageRequest {
     key: ImageKey,
     rendering: ImageRendering,
     tile: Option<TileOffset>,
@@ -202,17 +219,17 @@ pub struct ResourceCache {
 
     resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
-    cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
+    cached_glyph_dimensions: HashMap<GlyphRequest, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
     pending_image_requests: Vec<ImageRequest>,
     glyph_rasterizer: GlyphRasterizer,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
     blob_image_requests: HashSet<ImageRequest>,
 
     requested_glyphs: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
     requested_images: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
@@ -441,99 +458,82 @@ impl ResourceCache {
                 }
             }
         } else {
             self.pending_image_requests.push(request);
         }
     }
 
     pub fn request_glyphs(&mut self,
-                          key: FontKey,
-                          size: Au,
-                          mut color: ColorF,
-                          glyph_instances: &[GlyphInstance],
-                          render_mode: FontRenderMode,
-                          glyph_options: Option<GlyphOptions>) {
+                          font: FontInstanceKey,
+                          glyph_instances: &[GlyphInstance]) {
         debug_assert_eq!(self.state, State::AddResources);
 
-        // In alpha/mono mode, the color of the font is irrelevant.
-        // Forcing it to black in those cases saves rasterizing glyphs
-        // of different colors when not needed.
-        if render_mode != FontRenderMode::Subpixel {
-            color = BLACK;
-        }
-
         self.glyph_rasterizer.request_glyphs(
             &mut self.cached_glyphs,
             self.current_frame_id,
-            key,
-            size,
-            color,
+            font,
             glyph_instances,
-            render_mode,
-            glyph_options,
             &mut self.requested_glyphs,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
     pub fn get_glyphs<F>(&self,
-                         font_key: FontKey,
-                         size: Au,
-                         mut color: ColorF,
+                         font: FontInstanceKey,
                          glyph_instances: &[GlyphInstance],
-                         render_mode: FontRenderMode,
-                         glyph_options: Option<GlyphOptions>,
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
-        // Color when retrieving glyphs must match that of the request,
-        // otherwise the hash keys won't match.
-        if render_mode != FontRenderMode::Subpixel {
-            color = BLACK;
-        }
-
         debug_assert_eq!(self.state, State::QueryResources);
         let mut glyph_request = GlyphRequest::new(
-            font_key,
-            size,
-            color,
+            font,
             0,
             LayoutPoint::zero(),
-            render_mode,
-            glyph_options
         );
         let mut texture_id = None;
         for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
             glyph_request.key.index = glyph_instance.index;
-            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point, render_mode);
+            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point,
+                                                                  glyph_request.font.render_mode);
 
             let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
             let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
             if let Some(cache_item) = cache_item {
                 f(loop_index, &cache_item.uv_rect_handle);
                 debug_assert!(texture_id == None ||
                               texture_id == Some(cache_item.texture_id));
                 texture_id = Some(cache_item.texture_id);
             }
         }
 
         texture_id.map_or(SourceTexture::Invalid, SourceTexture::TextureCache)
     }
 
-    pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
-        match self.cached_glyph_dimensions.entry(glyph_key.clone()) {
+    pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
+                                glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
+        let key = GlyphRequest {
+            font: font.clone(),
+            key: glyph_key.clone(),
+        };
+
+        match self.cached_glyph_dimensions.entry(key.clone()) {
             Occupied(entry) => *entry.get(),
             Vacant(entry) => {
-                *entry.insert(self.glyph_rasterizer.get_glyph_dimensions(glyph_key))
+                *entry.insert(self.glyph_rasterizer.get_glyph_dimensions(&key.font, &key.key))
             }
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        self.glyph_rasterizer.get_glyph_index(font_key, ch)
+    }
+
     #[inline]
     pub fn get_cached_image(&self,
                             image_key: ImageKey,
                             image_rendering: ImageRendering,
                             tile: Option<TileOffset>) -> CacheItem {
         debug_assert_eq!(self.state, State::QueryResources);
         let key = ImageRequest {
             key: image_key,
@@ -778,16 +778,38 @@ impl ResourceCache {
             }
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
+
+    pub fn clear_namespace(&mut self, namespace: IdNamespace) {
+        //TODO: use `retain` when we are on Rust-1.18
+        let image_keys: Vec<_> = self.resources.image_templates.images.keys()
+                                                                      .filter(|&key| key.0 == namespace)
+                                                                      .cloned()
+                                                                      .collect();
+        for key in &image_keys {
+            self.resources.image_templates.images.remove(key);
+        }
+
+        let font_keys: Vec<_> = self.resources.font_templates.keys()
+                                                             .filter(|&key| key.0 == namespace)
+                                                             .cloned()
+                                                             .collect();
+        for key in &font_keys {
+            self.resources.font_templates.remove(key);
+        }
+
+        self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key.0 == namespace);
+        self.cached_glyphs.clear_keys(&mut self.texture_cache, |request| request.font.font_key.0 == namespace);
+    }
 }
 
 pub trait Resource {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId>;
 }
 
 impl Resource for TextureCacheItemId {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,18 +1,17 @@
 /* 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/. */
 
-use app_units::Au;
 use border::{BorderCornerInstance, BorderCornerSide};
 use device::TextureId;
 use fnv::FnvHasher;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheUpdateList};
-use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchTextures, CacheTextureId, LowLevelFilterOp};
+use internal_types::{BatchTextures, CacheTextureId};
 use internal_types::SourceTexture;
 use mask_cache::MaskCacheInfo;
 use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, ImagePrimitiveKind, PrimitiveCacheKey};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment, RenderTask, RenderTaskData};
 use render_task::{RenderTaskId, RenderTaskIndex, RenderTaskKey, RenderTaskKind};
 use render_task::RenderTaskLocation;
@@ -20,19 +19,19 @@ use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use texture_cache::TexturePage;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
-use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
-use api::{ExternalImageType, FontRenderMode, ImageRendering, LayerRect};
-use api::{LayerToWorldTransform, MixBlendMode, PipelineId, TransformStyle};
+use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstanceKey};
+use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
+use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
 use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_INDEX: RenderTaskIndex = RenderTaskIndex(i32::MAX as usize);
 
 
 pub type DisplayListMap = HashMap<PipelineId,
@@ -279,25 +278,27 @@ impl AlphaRenderItem {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = AlphaBatchKey::new(AlphaBatchKind::Blend,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::PremultipliedAlpha,
                                              BatchTextures::no_texture());
                 let src_task_index = render_tasks.get_static_task_index(&src_id);
 
                 let (filter_mode, amount) = match filter {
-                    LowLevelFilterOp::Blur(..) => (0, 0.0),
-                    LowLevelFilterOp::Contrast(amount) => (1, amount.to_f32_px()),
-                    LowLevelFilterOp::Grayscale(amount) => (2, amount.to_f32_px()),
-                    LowLevelFilterOp::HueRotate(angle) => (3, (angle as f32) / ANGLE_FLOAT_TO_FIXED),
-                    LowLevelFilterOp::Invert(amount) => (4, amount.to_f32_px()),
-                    LowLevelFilterOp::Saturate(amount) => (5, amount.to_f32_px()),
-                    LowLevelFilterOp::Sepia(amount) => (6, amount.to_f32_px()),
-                    LowLevelFilterOp::Brightness(amount) => (7, amount.to_f32_px()),
-                    LowLevelFilterOp::Opacity(amount) => (8, amount.to_f32_px()),
+                    // TODO: Implement blur filter #1351
+                    FilterOp::Blur(..) => (0, 0.0),
+                    FilterOp::Contrast(amount) => (1, amount),
+                    FilterOp::Grayscale(amount) => (2, amount),
+                    FilterOp::HueRotate(angle) => (3, angle),
+                    FilterOp::Invert(amount) => (4, amount),
+                    FilterOp::Saturate(amount) => (5, amount),
+                    FilterOp::Sepia(amount) => (6, amount),
+                    FilterOp::Brightness(amount) => (7, amount),
+                    FilterOp::Opacity(PropertyBinding::Value(amount)) => (8, amount),
+                    FilterOp::Opacity(_) => unreachable!(),
                 };
 
                 let amount = (amount * 65535.0).round() as i32;
                 let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
 
                 let instance = CompositePrimitiveInstance::new(task_index,
                                                                src_task_index,
                                                                RenderTaskIndex(0),
@@ -422,16 +423,21 @@ impl AlphaRenderItem {
                             }
                         });
                     }
                     PrimitiveKind::Rectangle => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Rectangle, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(0, 0, 0));
                     }
+                    PrimitiveKind::Line => {
+                        let key = AlphaBatchKey::new(AlphaBatchKind::Line, flags, blend_mode, no_textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(0, 0, 0));
+                    }
                     PrimitiveKind::Image => {
                         let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                         let (color_texture_id, uv_address) = match image_cpu.kind {
                             ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, _) => {
                                 resolve_image(image_key,
                                               image_rendering,
                                               tile_offset,
@@ -478,22 +484,25 @@ impl AlphaRenderItem {
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
-                        let texture_id = ctx.resource_cache.get_glyphs(text_cpu.font_key,
-                                                                       font_size_dp,
-                                                                       text_cpu.color,
+                        let font = FontInstanceKey::new(text_cpu.font_key,
+                                                        font_size_dp,
+                                                        text_cpu.color,
+                                                        text_cpu.normal_render_mode,
+                                                        text_cpu.glyph_options);
+
+                        let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                        &text_cpu.glyph_instances,
-                                                                       text_cpu.normal_render_mode,
-                                                                       text_cpu.glyph_options, |index, handle| {
+                                                                       |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
                             instances.push(base_instance.build(index as i32,
                                                                text_cpu.normal_render_mode as i32,
                                                                uv_address));
                         });
 
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
@@ -927,16 +936,17 @@ pub struct ColorRenderTarget {
     // TODO(gw): For now, assume that these all come from
     //           the same source texture id. This is almost
     //           always true except for pathological test
     //           cases with more than 4k x 4k of unique
     //           glyphs visible. Once the future glyph / texture
     //           cache changes land, this restriction will
     //           be removed anyway.
     pub text_run_cache_prims: Vec<PrimitiveInstance>,
+    pub line_cache_prims: Vec<PrimitiveInstance>,
     pub text_run_textures: BatchTextures,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurCommand>,
     pub horizontal_blurs: Vec<BlurCommand>,
     pub readbacks: Vec<DeviceIntRect>,
     allocator: TextureAllocator,
 }
 
@@ -945,16 +955,17 @@ impl RenderTarget for ColorRenderTarget 
         self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
             box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
+            line_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             allocator: TextureAllocator::new(size),
         }
     }
 
@@ -1035,38 +1046,40 @@ impl RenderTarget for ColorRenderTarget 
 
                         // todo(gw): avoid / recycle this allocation...
                         let mut instances = Vec::new();
 
                         let task_index = render_tasks.get_task_index(&task.id, pass_index);
 
                         for sub_prim_index in &prim.primitives {
                             let sub_metadata = ctx.prim_store.get_metadata(*sub_prim_index);
+                            let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache);
+                            let instance = SimplePrimitiveInstance::new(sub_prim_address,
+                                                                        task_index,
+                                                                        RenderTaskIndex(0),
+                                                                        PackedLayerIndex(0),
+                                                                        0);     // z is disabled for rendering cache primitives
+
                             match sub_metadata.prim_kind {
                                 PrimitiveKind::TextRun => {
                                     // Add instances that reference the text run GPU location. Also supply
                                     // the parent text-shadow prim address as a user data field, allowing
                                     // the shader to fetch the text-shadow parameters.
-                                    let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache);
                                     let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
-
-                                    let instance = SimplePrimitiveInstance::new(sub_prim_address,
-                                                                                task_index,
-                                                                                RenderTaskIndex(0),
-                                                                                PackedLayerIndex(0),
-                                                                                0);     // z is disabled for rendering cache primitives
-
                                     let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
-                                    let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
-                                                                                   font_size_dp,
-                                                                                   text.color,
+                                    let font = FontInstanceKey::new(text.font_key,
+                                                                    font_size_dp,
+                                                                    text.color,
+                                                                    text.shadow_render_mode,
+                                                                    text.glyph_options);
+
+                                    let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                                    &text.glyph_instances,
-                                                                                   text.shadow_render_mode,
-                                                                                   text.glyph_options, |index, handle| {
+                                                                                   |index, handle| {
                                         let uv_address = handle.as_int(gpu_cache);
                                         instances.push(instance.build(index as i32,
                                                                       uv_address,
                                                                       prim_address));
                                     });
 
                                     if texture_id != SourceTexture::Invalid {
                                         let textures = BatchTextures {
@@ -1077,16 +1090,19 @@ impl RenderTarget for ColorRenderTarget 
                                         instances.clear();
 
                                         debug_assert!(textures.colors[0] != SourceTexture::Invalid);
                                         debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
                                                       self.text_run_textures.colors[0] == textures.colors[0]);
                                         self.text_run_textures = textures;
                                     }
                                 }
+                                PrimitiveKind::Line => {
+                                    self.line_cache_prims.push(instance.build(prim_address, 0, 0));
+                                }
                                 _ => {
                                     unreachable!("Unexpected sub primitive type");
                                 }
                             }
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
@@ -1282,16 +1298,17 @@ pub enum AlphaBatchKind {
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     CacheImage,
     BorderCorner,
     BorderEdge,
+    Line,
 }
 
 bitflags! {
     pub struct AlphaBatchKeyFlags: u8 {
         const NEEDS_CLIPPING  = 0b00000001;
         const AXIS_ALIGNED    = 0b00000010;
     }
 }
@@ -1540,17 +1557,16 @@ pub struct StackingContext {
     pub screen_bounds: DeviceIntRect,
 
     /// Local bounding rectangle of this stacking context,
     /// computed as the union of all contained items that are not
     /// `ContextIsolation::Items` on their own
     pub isolated_items_bounds: LayerRect,
 
     pub composite_ops: CompositeOps,
-    pub clip_scroll_groups: Vec<ClipScrollGroupIndex>,
 
     /// Type of the isolation of the content.
     pub isolation: ContextIsolation,
 
     /// Set for the root stacking context of a display list or an iframe. Used for determining
     /// when to isolate a mix-blend-mode composite.
     pub is_page_root: bool,
 
@@ -1573,50 +1589,32 @@ impl StackingContext {
         };
         StackingContext {
             pipeline_id,
             reference_frame_offset,
             reference_frame_id,
             screen_bounds: DeviceIntRect::zero(),
             isolated_items_bounds: LayerRect::zero(),
             composite_ops,
-            clip_scroll_groups: Vec::new(),
             isolation,
             is_page_root,
             is_visible: false,
         }
     }
 
-    pub fn clip_scroll_group(&self, clip_and_scroll: ClipAndScrollInfo) -> ClipScrollGroupIndex {
-        // Currently there is only one scrolled stacking context per context,
-        // but eventually this will be selected from the vector based on the
-        // scroll layer of this primitive.
-        for group in &self.clip_scroll_groups {
-            if group.1 == clip_and_scroll {
-                return *group;
-            }
-        }
-        unreachable!("Looking for non-existent ClipScrollGroup");
-    }
-
     pub fn can_contribute_to_scene(&self) -> bool {
         !self.composite_ops.will_make_invisible()
     }
-
-    pub fn has_clip_scroll_group(&self, clip_and_scroll: ClipAndScrollInfo) -> bool {
-        self.clip_scroll_groups.iter().rev().any(|index| index.1 == clip_and_scroll)
-    }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct ClipScrollGroupIndex(pub usize, pub ClipAndScrollInfo);
 
 #[derive(Debug)]
 pub struct ClipScrollGroup {
-    pub stacking_context_index: StackingContextIndex,
     pub scroll_node_id: ClipId,
     pub clip_node_id: ClipId,
     pub packed_layer_index: PackedLayerIndex,
     pub screen_bounding_rect: Option<(TransformedRectKind, DeviceIntRect)>,
 }
 
 impl ClipScrollGroup {
     pub fn is_visible(&self) -> bool {
@@ -1668,37 +1666,37 @@ impl PackedLayer {
         xf_rect.bounding_rect.intersection(screen_rect)
                              .map(|rect| (xf_rect.kind, rect))
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
-    pub filters: Vec<LowLevelFilterOp>,
+    pub filters: Vec<FilterOp>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<LowLevelFilterOp>, mix_blend_mode: Option<MixBlendMode>) -> CompositeOps {
+    pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> CompositeOps {
         CompositeOps {
             filters,
             mix_blend_mode: mix_blend_mode
         }
     }
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
 
     pub fn will_make_invisible(&self) -> bool {
         for op in &self.filters {
-            if op == &LowLevelFilterOp::Opacity(Au(0)) {
+            if op == &FilterOp::Opacity(PropertyBinding::Value(0.0)) {
                 return true;
             }
         }
         false
     }
 }
 
 /// A rendering-oriented representation of frame::Frame built by the render backend
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -6,29 +6,31 @@ use channel::{self, MsgSender, Payload, 
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceIntSize};
 use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
 use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutVector2D, LayoutSize, LayoutTransform};
-use {NativeFontHandle, WorldPoint};
+use {FontInstanceKey, NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>, u32),
     AddNativeFont(FontKey, NativeFontHandle),
     DeleteFont(FontKey),
     /// Gets the glyph dimensions
-    GetGlyphDimensions(Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
+    GetGlyphDimensions(FontInstanceKey, Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
+    /// Gets the glyph indices from a string
+    GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
     /// Adds an image from the resource cache.
     AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
     /// Updates the the resource cache with the new image data.
     UpdateImage(ImageKey, ImageDescriptor, ImageData, Option<DeviceUintRect>),
     /// Drops an image from the resource cache.
     DeleteImage(ImageKey),
     CloneApi(MsgSender<IdNamespace>),
     /// Supplies a new frame to WebRender.
@@ -57,26 +59,29 @@ pub enum ApiMsg {
     WebGLCommand(WebGLContextId, WebGLCommand),
     GenerateFrame(Option<DynamicProperties>),
     // WebVR commands that must be called in the WebGL render thread.
     VRCompositorCommand(WebGLContextId, VRCompositorCommand),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
+    /// Remove all resources associated with this namespace.
+    ClearNamespace(IdNamespace),
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             ApiMsg::AddRawFont(..) => "ApiMsg::AddRawFont",
             ApiMsg::AddNativeFont(..) => "ApiMsg::AddNativeFont",
             ApiMsg::DeleteFont(..) => "ApiMsg::DeleteFont",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
+            ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
             ApiMsg::AddImage(..) => "ApiMsg::AddImage",
             ApiMsg::UpdateImage(..) => "ApiMsg::UpdateImage",
             ApiMsg::DeleteImage(..) => "ApiMsg::DeleteImage",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
             ApiMsg::SetDisplayList(..) => "ApiMsg::SetDisplayList",
             ApiMsg::SetRootPipeline(..) => "ApiMsg::SetRootPipeline",
             ApiMsg::Scroll(..) => "ApiMsg::Scroll",
             ApiMsg::ScrollNodeWithId(..) => "ApiMsg::ScrollNodeWithId",
@@ -89,16 +94,17 @@ impl fmt::Debug for ApiMsg {
             ApiMsg::GenerateFrame(..) => "ApiMsg::GenerateFrame",
             ApiMsg::VRCompositorCommand(..) => "ApiMsg::VRCompositorCommand",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
             ApiMsg::SetPageZoom(..) => "ApiMsg::SetPageZoom",
             ApiMsg::SetPinchZoom(..) => "ApiMsg::SetPinchZoom",
             ApiMsg::SetPan(..) => "ApiMsg::SetPan",
             ApiMsg::SetWindowParameters(..) => "ApiMsg::SetWindowParameters",
+            ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
         })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
@@ -120,17 +126,17 @@ pub enum WebGLCommand {
     Flush,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct PipelineId(pub u32, pub u32);
 
 #[repr(C)]
-#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
 pub struct IdNamespace(pub u32);
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct ResourceId(pub u32);
 
 /// An opaque pointer-sized value.
 #[repr(C)]
@@ -218,20 +224,33 @@ impl RenderApi {
         self.api_sender.send(msg).unwrap();
     }
 
     /// Gets the dimensions for the supplied glyph keys
     ///
     /// Note: Internally, the internal texture cache doesn't store
     /// 'empty' textures (height or width = 0)
     /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None.
-    pub fn get_glyph_dimensions(&self, glyph_keys: Vec<GlyphKey>)
+    pub fn get_glyph_dimensions(&self,
+                                font: FontInstanceKey,
+                                glyph_keys: Vec<GlyphKey>)
                                 -> Vec<Option<GlyphDimensions>> {
         let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::GetGlyphDimensions(glyph_keys, tx);
+        let msg = ApiMsg::GetGlyphDimensions(font, glyph_keys, tx);
+        self.api_sender.send(msg).unwrap();
+        rx.recv().unwrap()
+    }
+
+    /// Gets the glyph indices for the supplied string. These
+    /// can be used to construct GlyphKeys.
+    pub fn get_glyph_indices(&self,
+                             font_key: FontKey,
+                             text: &str) -> Vec<Option<u32>> {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        let msg = ApiMsg::GetGlyphIndices(font_key, text.to_string(), tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     /// Creates an `ImageKey`.
     pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
         ImageKey::new(new_id.0, new_id.1)
@@ -434,21 +453,26 @@ impl RenderApi {
                 namespace: new_id.0,
                 uid: new_id.1,
             },
             _phantom: PhantomData,
         }
     }
 
     #[inline]
-    fn next_unique_id(&self) -> (u32, u32) {
-        let IdNamespace(namespace) = self.id_namespace;
+    fn next_unique_id(&self) -> (IdNamespace, u32) {
         let ResourceId(id) = self.next_id.get();
         self.next_id.set(ResourceId(id + 1));
-        (namespace, id)
+        (self.id_namespace, id)
+    }
+}
+
+impl Drop for RenderApi {
+    fn drop(&mut self) {
+        let _ = self.api_sender.send(ApiMsg::ClearNamespace(self.id_namespace));
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum ScrollEventPhase {
     /// The user started scrolling.
     Start,
     /// The user performed a scroll. The Boolean flag indicates whether the user's fingers are
@@ -487,24 +511,24 @@ impl ZoomFactor {
     /// Get the zoom factor as an untyped float.
     pub fn get(&self) -> f32 {
         self.0
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)]
 pub struct PropertyBindingId {
-    namespace: u32,
+    namespace: IdNamespace,
     uid: u32,
 }
 
 impl PropertyBindingId {
     pub fn new(value: u64) -> Self {
         PropertyBindingId {
-            namespace: (value>>32) as u32,
+            namespace: IdNamespace((value>>32) as u32),
             uid: value as u32,
         }
     }
 }
 
 /// A unique key that is used for connecting animated property
 /// values to bindings in the display list.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/color.rs
+++ b/gfx/webrender_api/src/color.rs
@@ -1,74 +1,105 @@
 /* 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/. */
 
+/// Represents RGBA screen colors with floating point numbers.
+///
+/// All components must be between 0.0 and 1.0.
+/// An alpha value of 1.0 is opaque while 0.0 is fully transparent.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ColorF {
     pub r: f32,
     pub g: f32,
     pub b: f32,
     pub a: f32,
 }
 known_heap_size!(0, ColorF);
 
 impl ColorF {
-    pub fn premultiplied(&self) -> ColorF {
+    /// Constructs a new `ColorF` from its components.
+    pub fn new(r: f32, g: f32, b: f32, a: f32) -> ColorF {
         ColorF {
-            r: self.r * self.a,
-            g: self.g * self.a,
-            b: self.b * self.a,
+            r,
+            g,
+            b,
+            a,
+        }
+    }
+
+    /// Multiply the RGB channels (but not alpha) with a given factor.
+    pub fn scale_rgb(&self, scale: f32) -> ColorF {
+        ColorF {
+            r: self.r * scale,
+            g: self.g * scale,
+            b: self.b * scale,
             a: self.a,
         }
     }
+
+    pub fn to_array(&self) -> [f32; 4] {
+        [self.r, self.g, self.b, self.a]
+    }
+
+    /// Multiply the RGB components with the alpha channel.
+    ///
+    /// In premultiplied colors transistions to transparent always look "nice"
+    /// therefore they are used in CSS gradients.
+    pub fn premultiplied(&self) -> ColorF {
+        self.scale_rgb(self.a)
+    }
 }
 
+/// Represents RGBA screen colors with one byte per channel.
+///
+/// If the alpha value `a` is 255 the color is opaque.
 #[repr(C)]
 #[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct ColorU {
     pub r: u8,
     pub g: u8,
     pub b: u8,
     pub a: u8,
 }
 
-impl From<ColorF> for ColorU {
-    fn from(color: ColorF) -> ColorU {
-        ColorU {
-            r: ColorU::round_to_int(color.r),
-            g: ColorU::round_to_int(color.g),
-            b: ColorU::round_to_int(color.b),
-            a: ColorU::round_to_int(color.a),
-        }
-    }
-}
-
-impl Into<ColorF> for ColorU {
-    fn into(self) -> ColorF {
-        ColorF {
-            r: self.r as f32 / 255.0,
-            g: self.g as f32 / 255.0,
-            b: self.b as f32 / 255.0,
-            a: self.a as f32 / 255.0,
-        }
-    }
-}
-
 impl ColorU {
-    fn round_to_int(x: f32) -> u8 {
-        debug_assert!((0.0 <= x) && (x <= 1.0));
-        let f = (255.0 * x) + 0.5;
-        let val = f.floor();
-        debug_assert!(val <= 255.0);
-        val as u8
-    }
-
+    /// Constructs a new additive `ColorU` from its components.
     pub fn new(r: u8, g: u8, b: u8, a: u8) -> ColorU {
         ColorU {
             r,
             g,
             b,
             a,
         }
     }
 }
+
+fn round_to_int(x: f32) -> u8 {
+    debug_assert!((0.0 <= x) && (x <= 1.0));
+    let f = (255.0 * x) + 0.5;
+    let val = f.floor();
+    debug_assert!(val <= 255.0);
+    val as u8
+}
+
+impl From<ColorF> for ColorU {
+    fn from(color: ColorF) -> ColorU {
+        ColorU {
+            r: round_to_int(color.r),
+            g: round_to_int(color.g),
+            b: round_to_int(color.b),
+            a: round_to_int(color.a),
+        }
+    }
+}
+
+impl From<ColorU> for ColorF {
+    fn from(color: ColorU) -> ColorF {
+        ColorF {
+            r: color.r as f32 / 255.0,
+            g: color.g as f32 / 255.0,
+            b: color.b as f32 / 255.0,
+            a: color.a as f32 / 255.0,
+        }
+    }
+}
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -1,16 +1,16 @@
 /* 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/. */
 
 use app_units::Au;
 use euclid::SideOffsets2D;
 use {ColorF, FontKey, ImageKey, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
-use {LayoutVector2D, PipelineId, PropertyBinding, WebGLContextId};
+use {GlyphOptions, LayoutVector2D, PipelineId, PropertyBinding, WebGLContextId};
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipAndScrollInfo {
@@ -44,18 +44,19 @@ pub struct DisplayItem {
     pub rect: LayoutRect,
     pub local_clip: LocalClip,
     pub clip_and_scroll: ClipAndScrollInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
-    ScrollFrame(ClipDisplayItem),
+    ScrollFrame(ScrollFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
+    Line(LineDisplayItem),
     Text(TextDisplayItem),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     WebGL(WebGLDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
@@ -72,41 +73,74 @@ pub enum SpecificDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub parent_id: ClipId,
     pub image_mask: Option<ImageMask>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum ScrollSensitivity {
+    ScriptAndInputEvents,
+    Script,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ScrollFrameDisplayItem {
+    pub id: ClipId,
+    pub parent_id: ClipId,
+    pub image_mask: Option<ImageMask>,
+    pub scroll_sensitivity: ScrollSensitivity,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct LineDisplayItem {
+    pub baseline: f32, // LayerPixel
+    pub start: f32,
+    pub end: f32,
+    pub orientation: LineOrientation, // toggles whether above values are interpreted as x/y values
+    pub width: f32,
+    pub color: ColorF,
+    pub style: LineStyle,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum LineOrientation {
+    Vertical,
+    Horizontal,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum LineStyle {
+    Solid,
+    Dotted,
+    Dashed,
+    Wavy,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct TextDisplayItem {
     pub font_key: FontKey,
     pub size: Au,
     pub color: ColorF,
     pub glyph_options: Option<GlyphOptions>,
 } // IMPLICIT: glyphs: Vec<GlyphInstance>
 
-#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
-pub struct GlyphOptions {
-    // These are currently only used on windows for dwrite fonts.
-    pub use_embedded_bitmap: bool,
-    pub force_gdi_rendering: bool,
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct WebGLDisplayItem {
     pub context_id: WebGLContextId,
 }
 
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct NormalBorder {
     pub left: BorderSide,
     pub right: BorderSide,
     pub top: BorderSide,
     pub bottom: BorderSide,
     pub radius: BorderRadius,
 }
@@ -325,33 +359,33 @@ pub enum MixBlendMode {
     Difference  = 10,
     Exclusion   = 11,
     Hue         = 12,
     Saturation  = 13,
     Color       = 14,
     Luminosity  = 15,
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
-    Blur(Au),
+    Blur(f32),
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>),
     Saturate(f32),
     Sepia(f32),
 }
 
 impl FilterOp {
     pub fn is_noop(&self) -> bool {
         match *self {
-            FilterOp::Blur(length) if length == Au(0) => true,
+            FilterOp::Blur(length) if length == 0.0 => true,
             FilterOp::Brightness(amount) if amount == 1.0 => true,
             FilterOp::Contrast(amount) if amount == 1.0 => true,
             FilterOp::Grayscale(amount) if amount == 0.0 => true,
             FilterOp::HueRotate(amount) if amount == 0.0 => true,
             FilterOp::Invert(amount) if amount == 0.0 => true,
             FilterOp::Opacity(amount) if amount == PropertyBinding::Value(1.0) => true,
             FilterOp::Saturate(amount) if amount == 1.0 => true,
             FilterOp::Sepia(amount) if amount == 0.0 => true,
@@ -470,16 +504,29 @@ impl From<LayoutRect> for LocalClip {
 
 impl LocalClip {
     pub fn clip_rect(&self) -> &LayoutRect {
         match *self {
             LocalClip::Rect(ref rect) => rect,
             LocalClip::RoundedRect(ref rect, _) => &rect,
         }
     }
+
+    pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
+        match *self {
+            LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
+            LocalClip::RoundedRect(rect, complex) => {
+                LocalClip::RoundedRect(rect.translate(offset),
+                                       ComplexClipRegion {
+                                            rect: complex.rect.translate(offset),
+                                            radii: complex.radii,
+                                        })
+            }
+        }
+    }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ComplexClipRegion {
     /// The boundaries of the rectangle.
     pub rect: LayoutRect,
     /// Border radii of this rectangle.
     pub radii: BorderRadius,
@@ -535,40 +582,16 @@ impl BorderRadius {
         if let Some(radius) = self.is_uniform() {
             radius == 0.0
         } else {
             false
         }
     }
 }
 
-impl ColorF {
-    pub fn new(r: f32, g: f32, b: f32, a: f32) -> ColorF {
-        ColorF {
-            r,
-            g,
-            b,
-            a,
-        }
-    }
-
-    pub fn scale_rgb(&self, scale: f32) -> ColorF {
-        ColorF {
-            r: self.r * scale,
-            g: self.g * scale,
-            b: self.b * scale,
-            a: self.a,
-        }
-    }
-
-    pub fn to_array(&self) -> [f32; 4] {
-        [self.r, self.g, self.b, self.a]
-    }
-}
-
 impl ComplexClipRegion {
     /// Create a new complex clip region.
     pub fn new(rect: LayoutRect, radii: BorderRadius) -> ComplexClipRegion {
         ComplexClipRegion {
             rect,
             radii,
         }
     }
@@ -641,8 +664,9 @@ macro_rules! define_empty_heap_size_of {
 }
 
 define_empty_heap_size_of!(ClipId);
 define_empty_heap_size_of!(RepeatMode);
 define_empty_heap_size_of!(ImageKey);
 define_empty_heap_size_of!(MixBlendMode);
 define_empty_heap_size_of!(TransformStyle);
 define_empty_heap_size_of!(LocalClip);
+define_empty_heap_size_of!(ScrollSensitivity);
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -6,21 +6,22 @@ use app_units::Au;
 use bincode;
 use serde::{Deserialize, Serialize, Serializer};
 use serde::ser::{SerializeSeq, SerializeMap};
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
 use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
 use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip};
-use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
-use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
-use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem};
-use {YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LineDisplayItem};
+use {LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId, PropertyBinding};
+use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
+use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
+use {SpecificDisplayItem, StackingContext, TextDisplayItem, TextShadow, TransformStyle};
+use {WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
@@ -53,16 +54,18 @@ pub struct BuiltDisplayList {
 /// items.
 #[repr(C)]
 #[derive(Copy, Clone, Default, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
     /// The first IPC time stamp: before any work has been done
     builder_start_time: u64,
     /// The second IPC time stamp: after serialization
     builder_finish_time: u64,
+    /// The third IPC time stamp: just before sending
+    send_start_time: u64,
 }
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
@@ -95,30 +98,33 @@ impl BuiltDisplayListDescriptor {
 impl BuiltDisplayList {
     pub fn from_data(data: Vec<u8>, descriptor: BuiltDisplayListDescriptor) -> BuiltDisplayList {
         BuiltDisplayList {
             data,
             descriptor,
         }
     }
 
-    pub fn into_data(self) -> (Vec<u8>, BuiltDisplayListDescriptor) {
+    pub fn into_data(mut self) -> (Vec<u8>, BuiltDisplayListDescriptor) {
+        self.descriptor.send_start_time = precise_time_ns();
         (self.data, self.descriptor)
     }
 
     pub fn data(&self) -> &[u8] {
         &self.data[..]
     }
 
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
-    pub fn times(&self) -> (u64, u64) {
-      (self.descriptor.builder_start_time, self.descriptor.builder_finish_time)
+    pub fn times(&self) -> (u64, u64, u64) {
+      (self.descriptor.builder_start_time,
+       self.descriptor.builder_finish_time,
+       self.descriptor.send_start_time)
     }
 
     pub fn iter(&self) -> BuiltDisplayListIter {
         BuiltDisplayListIter::new(self)
     }
 
     pub fn get<'de, T: Deserialize<'de>>(&self, range: ItemRange<T>) -> AuxIter<T> {
         AuxIter::new(&self.data[range.start .. range.start + range.length])
@@ -271,16 +277,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn rect(&self) -> LayoutRect {
         self.iter.cur_item.rect
     }
 
     pub fn local_clip(&self) -> &LocalClip {
         &self.iter.cur_item.local_clip
     }
 
+    pub fn local_clip_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
+        self.iter.cur_item.local_clip.create_with_offset(offset)
+    }
+
     pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
         self.iter.cur_item.clip_and_scroll
     }
 
     pub fn item(&self) -> &SpecificDisplayItem {
         &self.iter.cur_item.item
     }
 
@@ -489,16 +499,33 @@ impl DisplayListBuilder {
     pub fn push_rect(&mut self, rect: LayoutRect, local_clip: Option<LocalClip>, color: ColorF) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem {
             color,
         });
 
         self.push_item(item, rect, local_clip);
     }
 
+    pub fn push_line(&mut self,
+                     local_clip: Option<LocalClip>,
+                     baseline: f32,
+                     start: f32,
+                     end: f32,
+                     orientation: LineOrientation,
+                     width: f32,
+                     color: ColorF,
+                     style: LineStyle) {
+        let item = SpecificDisplayItem::Line(LineDisplayItem {
+            baseline, start, end, orientation,
+            width, color, style,
+        });
+
+        self.push_item(item, LayoutRect::zero(), local_clip);
+    }
+
     pub fn push_image(&mut self,
                       rect: LayoutRect,
                       local_clip: Option<LocalClip>,
                       stretch_size: LayoutSize,
                       tile_spacing: LayoutSize,
                       image_rendering: ImageRendering,
                       key: ImageKey) {
         let item = SpecificDisplayItem::Image(ImageDisplayItem {
@@ -841,25 +868,27 @@ impl DisplayListBuilder {
         })
     }
 
     pub fn define_scroll_frame<I>(&mut self,
                                   id: Option<ClipId>,
                                   content_rect: LayoutRect,
                                   clip_rect: LayoutRect,
                                   complex_clips: I,
-                                  image_mask: Option<ImageMask>)
+                                  image_mask: Option<ImageMask>,
+                                  scroll_sensitivity: ScrollSensitivity)
                                   -> ClipId
                                   where I: IntoIterator<Item = ComplexClipRegion>,
                                         I::IntoIter: ExactSizeIterator {
         let id = self.generate_clip_id(id);
-        let item = SpecificDisplayItem::ScrollFrame(ClipDisplayItem {
+        let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             id: id,
             parent_id: self.clip_stack.last().unwrap().scroll_node_id,
             image_mask: image_mask,
+            scroll_sensitivity,
         });
 
         self.push_item(item, content_rect, Some(LocalClip::from(clip_rect)));
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_clip<I>(&mut self,
@@ -932,13 +961,14 @@ impl DisplayListBuilder {
         let end_time = precise_time_ns();
 
         (self.pipeline_id,
          self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: self.builder_start_time,
                 builder_finish_time: end_time,
+                send_start_time: 0,
             },
             data: self.data,
          })
     }
 }
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -1,14 +1,14 @@
 /* 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/. */
 
 use app_units::Au;
-use {ColorU, ColorF, LayoutPoint};
+use {ColorU, ColorF, IdNamespace, LayoutPoint};
 use std::sync::Arc;
 
 #[cfg(target_os = "macos")] use core_foundation::string::CFString;
 #[cfg(target_os = "macos")] use core_graphics::font::CGFont;
 #[cfg(target_os = "macos")] use serde::de::{self, Deserialize, Deserializer};
 #[cfg(target_os = "macos")] use serde::ser::{Serialize, Serializer};
 #[cfg(target_os = "windows")] use dwrote::FontDescriptor;
 
@@ -47,25 +47,26 @@ pub type NativeFontHandle = FontDescript
 
 #[repr(C)]
 #[derive(Copy, Clone, Deserialize, Serialize, Debug)]
 pub struct GlyphDimensions {
     pub left: i32,
     pub top: i32,
     pub width: u32,
     pub height: u32,
+    pub advance: f32,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Ord, PartialOrd)]
-pub struct FontKey(pub u32, pub u32);
+pub struct FontKey(pub IdNamespace, pub u32);
 
 impl FontKey {
-    pub fn new(key0: u32, key1: u32) -> FontKey {
-        FontKey(key0, key1)
+    pub fn new(namespace: IdNamespace, key: u32) -> FontKey {
+        FontKey(namespace, key)
     }
 }
 
 
 #[derive(Clone)]
 pub enum FontTemplate {
     Raw(Arc<Vec<u8>>, u32),
     Native(NativeFontHandle),
@@ -147,41 +148,71 @@ impl SubpixelPoint {
         }
     }
 
     pub fn to_f64(&self) -> (f64, f64) {
         (self.x.into(), self.y.into())
     }
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct GlyphOptions {
+    // These are currently only used on windows for dwrite fonts.
+    pub use_embedded_bitmap: bool,
+    pub force_gdi_rendering: bool,
+}
+
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
-pub struct GlyphKey {
+pub struct FontInstanceKey {
     pub font_key: FontKey,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
+    pub color: ColorU,
+    pub render_mode: FontRenderMode,
+    pub glyph_options: Option<GlyphOptions>,
+}
+
+impl FontInstanceKey {
+    pub fn new(font_key: FontKey,
+               size: Au,
+               mut color: ColorF,
+               render_mode: FontRenderMode,
+               glyph_options: Option<GlyphOptions>) -> FontInstanceKey {
+        // In alpha/mono mode, the color of the font is irrelevant.
+        // Forcing it to black in those cases saves rasterizing glyphs
+        // of different colors when not needed.
+        if render_mode != FontRenderMode::Subpixel {
+            color = ColorF::new(0.0, 0.0, 0.0, 1.0);
+        }
+
+        FontInstanceKey {
+            font_key,
+            size,
+            color: color.into(),
+            render_mode,
+            glyph_options,
+        }
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
+pub struct GlyphKey {
     pub index: u32,
-    pub color: ColorU,
     pub subpixel_point: SubpixelPoint,
 }
 
 impl GlyphKey {
-    pub fn new(font_key: FontKey,
-               size: Au,
-               color: ColorF,
-               index: u32,
+    pub fn new(index: u32,
                point: LayoutPoint,
                render_mode: FontRenderMode) -> GlyphKey {
         GlyphKey {
-            font_key,
-            size,
-            color: ColorU::from(color),
             index,
             subpixel_point: SubpixelPoint::new(point, render_mode),
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -1,24 +1,29 @@
 /* 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/. */
 
 use std::sync::Arc;
 use {DeviceUintRect, DevicePoint};
+use {IdNamespace};
 use {TileOffset, TileSize};
 use font::{FontKey, FontTemplate};
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub struct ImageKey(pub u32, pub u32);
+pub struct ImageKey(pub IdNamespace, pub u32);
 
 impl ImageKey {
-    pub fn new(key0: u32, key1: u32) -> ImageKey {
-        ImageKey(key0, key1)
+    pub fn new(namespace: IdNamespace, key: u32) -> ImageKey {
+        ImageKey(namespace, key)
+    }
+
+    pub fn dummy() -> ImageKey {
+        ImageKey(IdNamespace(0), 0)
     }
 }
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -104,36 +104,36 @@ struct ImageDescriptor: public wr::WrIma
 
 // Whenever possible, use wr::WindowId instead of manipulating uint64_t.
 inline uint64_t AsUint64(const WindowId& aId) {
   return static_cast<uint64_t>(aId.mHandle);
 }
 
 // Whenever possible, use wr::ImageKey instead of manipulating uint64_t.
 inline uint64_t AsUint64(const ImageKey& aId) {
-  return (static_cast<uint64_t>(aId.mNamespace) << 32)
+  return (static_cast<uint64_t>(aId.mNamespace.mHandle) << 32)
         + static_cast<uint64_t>(aId.mHandle);
 }
 
 inline ImageKey AsImageKey(const uint64_t& aId) {
   ImageKey imageKey;
-  imageKey.mNamespace = aId >> 32;
+  imageKey.mNamespace.mHandle = aId >> 32;
   imageKey.mHandle = aId;
   return imageKey;
 }
 
 // Whenever possible, use wr::FontKey instead of manipulating uint64_t.
 inline uint64_t AsUint64(const FontKey& aId) {
-  return (static_cast<uint64_t>(aId.mNamespace) << 32)
+  return (static_cast<uint64_t>(aId.mNamespace.mHandle) << 32)
         + static_cast<uint64_t>(aId.mHandle);
 }
 
 inline FontKey AsFontKey(const uint64_t& aId) {
   FontKey fontKey;
-  fontKey.mNamespace = aId >> 32;
+  fontKey.mNamespace.mHandle = aId >> 32;
   fontKey.mHandle = aId;
   return fontKey;
 }
 
 // Whenever possible, use wr::PipelineId instead of manipulating uint64_t.
 inline uint64_t AsUint64(const PipelineId& aId) {
   return (static_cast<uint64_t>(aId.mNamespace) << 32)
         + static_cast<uint64_t>(aId.mHandle);
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -936,17 +936,17 @@ pub extern "C" fn wr_dp_push_stacking_co
                                               filter_count: usize) {
     assert!(unsafe { !is_in_render_thread() });
 
     let bounds = bounds.into();
 
     let c_filters = make_slice(filters, filter_count);
     let mut filters : Vec<FilterOp> = c_filters.iter().map(|c_filter| {
         match c_filter.filter_type {
-            WrFilterOpType::Blur => FilterOp::Blur(Au::from_f32_px(c_filter.argument)),
+            WrFilterOpType::Blur => FilterOp::Blur(c_filter.argument),
             WrFilterOpType::Brightness => FilterOp::Brightness(c_filter.argument),
             WrFilterOpType::Contrast => FilterOp::Contrast(c_filter.argument),
             WrFilterOpType::Grayscale => FilterOp::Grayscale(c_filter.argument),
             WrFilterOpType::HueRotate => FilterOp::HueRotate(c_filter.argument),
             WrFilterOpType::Invert => FilterOp::Invert(c_filter.argument),
             WrFilterOpType::Opacity => FilterOp::Opacity(PropertyBinding::Value(c_filter.argument)),
             WrFilterOpType::Saturate => FilterOp::Saturate(c_filter.argument),
             WrFilterOpType::Sepia => FilterOp::Sepia(c_filter.argument),
@@ -1028,17 +1028,19 @@ pub extern "C" fn wr_dp_push_scroll_laye
     assert!(unsafe { is_in_main_thread() });
     let clip_id = ClipId::new(scroll_id, state.pipeline_id);
     // Avoid defining multiple scroll clips with the same clip id, as that
     // results in undefined behaviour or assertion failures.
     if !state.frame_builder.scroll_clips_defined.contains(&clip_id) {
         let content_rect: LayoutRect = content_rect.into();
         let clip_rect: LayoutRect = clip_rect.into();
 
-        state.frame_builder.dl_builder.define_scroll_frame(Some(clip_id), content_rect, clip_rect, vec![], None);
+        state.frame_builder.dl_builder.define_scroll_frame(
+            Some(clip_id), content_rect, clip_rect, vec![], None,
+            ScrollSensitivity::Script);
         state.frame_builder.scroll_clips_defined.insert(clip_id);
     }