Merge mozilla-central to inbound. r=merge a=merge on a CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Wed, 06 Dec 2017 01:49:19 +0200
changeset 449494 78a9a1962684f7572fa9f1d832335ca950edc312
parent 449493 2490244fe65d3760f341556602068a201b6dbb5a (current diff)
parent 449381 a928be5dacc3b544e29c0612b3f8cda6447df802 (diff)
child 449495 baf9a0e8573052ae16156ea97de1af344406eb7e
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.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 mozilla-central to inbound. r=merge a=merge on a CLOSED TREE
dom/base/Element.h
gfx/vr/VRDisplayHost.h
gfx/vr/gfxVROSVR.cpp
gfx/vr/gfxVROculus.cpp
gfx/vr/gfxVROculus.h
gfx/vr/gfxVROpenVR.cpp
gfx/vr/gfxVROpenVR.h
gfx/vr/gfxVRPuppet.cpp
gfx/webrender/res/ps_rectangle.glsl
layout/style/nsDOMCSSDeclaration.h
memory/replace/logalloc/logalloc.mozbuild
memory/replace/logalloc/minimal/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BitmapUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImage.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImageGLInfo.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FloatSize.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/IntSize.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PointUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RenderTask.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ViewTransform.java
mobile/android/themes/core/aboutReaderContent.css
mobile/android/themes/core/aboutReaderControls.css
modules/libpref/init/all.js
taskcluster/ci/test/talos.yml
toolkit/themes/shared/aboutReaderContent.css
toolkit/themes/shared/aboutReaderControls.css
toolkit/themes/shared/narrateControls.css
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,10 +19,32 @@ module.exports = {
 
   "overrides": [{
     // eslint-plugin-html handles eol-last slightly different - it applies to
     // each set of script tags, so we turn it off here.
     "files": "**/*.*html",
     "rules": {
       "eol-last": "off",
     }
+  }, {
+    // XXX Bug 1421969. These files/directories are still being fixed,
+    // so turn off mozilla/use-services for them for now.
+    "files": [
+      "accessible/**",
+      // Browser: Bug 1421379
+      "browser/extensions/shield-recipe-client/test/browser/head.js",
+      "browser/modules/offlineAppCache.jsm",
+      "chrome/**",
+      "devtools/**",
+      "dom/indexedDB/**",
+      "dom/media/**",
+      "extensions/pref/**",
+      "mobile/android/**",
+      "security/**",
+      "testing/**",
+      "tools/profiler/**",
+      "xpcom/**"
+    ],
+    "rules": {
+      "mozilla/use-services": "off",
+    }
   }]
 };
--- a/accessible/windows/msaa/RootAccessibleWrap.cpp
+++ b/accessible/windows/msaa/RootAccessibleWrap.cpp
@@ -144,8 +144,57 @@ RootAccessibleWrap::accNavigate(
     return E_FAIL;
   }
 
   VariantInit(pvarEndUpAt);
   pvarEndUpAt->pdispVal = NativeAccessible(target);
   pvarEndUpAt->vt = VT_DISPATCH;
   return S_OK;
 }
+
+STDMETHODIMP
+RootAccessibleWrap::get_accFocus(
+      /* [retval][out] */ VARIANT __RPC_FAR *pvarChild)
+{
+  HRESULT hr = DocAccessibleWrap::get_accFocus(pvarChild);
+  if (FAILED(hr) || pvarChild->vt != VT_EMPTY) {
+    // We got a definite result (either failure or an accessible).
+    return hr;
+  }
+
+  // The base implementation reported no focus.
+  // Focus might be in a remote document.
+  // (The base implementation can't handle this.)
+  // Get the document in the active tab.
+  ProxyAccessible* docProxy = GetPrimaryRemoteTopLevelContentDoc();
+  if (!docProxy) {
+    return hr;
+  }
+  Accessible* docAcc = WrapperFor(docProxy);
+  if (!docAcc) {
+    return E_FAIL;
+  }
+  RefPtr<IDispatch> docDisp = NativeAccessible(docAcc);
+  if (!docDisp) {
+    return E_FAIL;
+  }
+  RefPtr<IAccessible> docIa;
+  hr = docDisp->QueryInterface(IID_IAccessible, (void**)getter_AddRefs(docIa));
+  MOZ_ASSERT(SUCCEEDED(hr));
+  MOZ_ASSERT(docIa);
+
+  // Ask this document for its focused descendant.
+  // We return this as is to the client except for CHILDID_SELF (see below).
+  hr = docIa->get_accFocus(pvarChild);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  if (pvarChild->vt == VT_I4 && pvarChild->lVal == CHILDID_SELF) {
+    // The document itself has focus.
+    // We're handling a call to accFocus on the root accessible,
+    // so replace CHILDID_SELF with the document accessible.
+    pvarChild->vt = VT_DISPATCH;
+    docDisp.forget(&pvarChild->pdispVal);
+  }
+
+  return S_OK;
+}
--- a/accessible/windows/msaa/RootAccessibleWrap.h
+++ b/accessible/windows/msaa/RootAccessibleWrap.h
@@ -38,16 +38,19 @@ public:
    */
   already_AddRefed<IUnknown> GetInternalUnknown();
 
   virtual /* [id] */ HRESULT STDMETHODCALLTYPE accNavigate(
     /* [in] */ long navDir,
     /* [optional][in] */ VARIANT varStart,
     /* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt) override;
 
+  virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accFocus(
+    /* [retval][out] */ VARIANT __RPC_FAR *pvarChild) override;
+
 private:
   // DECLARE_AGGREGATABLE declares the internal IUnknown methods as well as
   // mInternalUnknown.
   DECLARE_AGGREGATABLE(RootAccessibleWrap);
   IUnknown* mOuter;
 };
 
 } // namespace a11y
--- a/browser/components/.eslintrc.js
+++ b/browser/components/.eslintrc.js
@@ -1,11 +1,9 @@
 "use strict";
 
 module.exports = {
   rules: {
     // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
     // be removed & synced with the mozilla/recommended value.
     "complexity": ["error", 61],
-
-    "mozilla/use-services": "error",
   }
 };
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2199,21 +2199,17 @@ BrowserGlue.prototype = {
         // Only reset the current engine if it wasn't set by a WebExtension
         // and it is not one of the default engines.
         if (currentEngine._extensionID || currentEngine._isDefault)
           return;
 
         if (currentEngine._loadPath.startsWith("[https]")) {
           Services.prefs.setCharPref("browser.search.reset.status", "pending");
         } else {
-          // Can't call resetToOriginalDefaultEngine because it doesn't
-          // unhide the engine.
-          let defaultEngine = Services.search.originalDefaultEngine;
-          defaultEngine.hidden = false;
-          Services.search.currentEngine = defaultEngine;
+          Services.search.resetToOriginalDefaultEngine();
           Services.prefs.setCharPref("browser.search.reset.status", "silent");
         }
       });
     }
 
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
--- a/browser/components/syncedtabs/SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -68,16 +68,21 @@ SyncedTabsDeckComponent.prototype = {
   get container() {
     return this._deckView ? this._deckView.container : null;
   },
 
   init() {
     Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
     Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
     Services.obs.addObserver(this, "weave:service:login:change");
+    // If the Sync service is not ready, in init() > updatePanel() we will
+    // show a blank screen. If tab syncing is disabled, we will not get any other
+    // ui-refreshing notifications! We listen to :ready in order to check again
+    // if this engine is disabled and refresh the UI one last time.
+    Services.obs.addObserver(this, "weave:service:ready");
 
     // Go ahead and trigger sync
     this._SyncedTabs.syncTabs()
                     .catch(Cu.reportError);
 
     this._deckView = new this._DeckView(this._window, this.tabListComponent, {
       onConnectDeviceClick: event => this.openConnectDevice(event),
       onSyncPrefClick: event => this.openSyncPrefs(event)
@@ -90,25 +95,29 @@ SyncedTabsDeckComponent.prototype = {
     // Set the initial panel to display
     this.updatePanel();
   },
 
   uninit() {
     Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
     Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
     Services.obs.removeObserver(this, "weave:service:login:change");
+    Services.obs.removeObserver(this, "weave:service:ready");
     this._deckView.destroy();
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case this._SyncedTabs.TOPIC_TABS_CHANGED:
         this._syncedTabsListStore.getData();
         this.updatePanel();
         break;
+      case "weave:service:ready":
+        Services.obs.removeObserver(this, "weave:service:ready");
+        // Intended fallthrough.
       case FxAccountsCommon.ONLOGIN_NOTIFICATION:
       case "weave:service:login:change":
         this.updatePanel();
         break;
       default:
         break;
     }
   },
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -106,43 +106,51 @@ AutofillProfileAutoCompleteSearch.protot
     let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
     let isInputAutofilled = info.state == FIELD_STATES.AUTO_FILLED;
     let handler = FormAutofillContent.getFormHandler(focusedInput);
     let allFieldNames = handler.getAllFieldNames(focusedInput);
     let filledRecordGUID = handler.getFilledRecordGUID(focusedInput);
     let searchPermitted = isAddressField ?
                           FormAutofillUtils.isAutofillAddressesEnabled :
                           FormAutofillUtils.isAutofillCreditCardsEnabled;
+    let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult;
 
     ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = focusedInput;
     // Fallback to form-history if ...
     //   - specified autofill feature is pref off.
     //   - no profile can fill the currently-focused input.
     //   - the current form has already been populated.
     //   - (address only) less than 3 inputs are covered by all saved fields in the storage.
     if (!searchPermitted || !savedFieldNames.has(info.fieldName) ||
         (!isInputAutofilled && filledRecordGUID) || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
       if (focusedInput.autocomplete == "off") {
-        // Create a dummy AddressResult as an empty search result.
-        let result = new AddressResult("", "", [], [], {});
+        // Create a dummy result as an empty search result.
+        let result = new AutocompleteResult("", "", [], [], {});
         listener.onSearchResult(this, result);
         return;
       }
       let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
                           .createInstance(Ci.nsIAutoCompleteSearch);
       formHistory.startSearch(searchString, searchParam, previousResult, {
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.lastProfileAutoCompleteResult = result;
         },
       });
       return;
     }
 
+    if (isInputAutofilled) {
+      let result = new AutocompleteResult(searchString, "", [], [], {isInputAutofilled});
+      listener.onSearchResult(this, result);
+      ProfileAutocomplete.lastProfileAutoCompleteResult = result;
+      return;
+    }
+
     let infoWithoutElement = Object.assign({}, info);
     delete infoWithoutElement.elementWeakRef;
 
     let data = {
       collectionName: isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME,
       info: infoWithoutElement,
       searchString,
     };
@@ -151,31 +159,23 @@ AutofillProfileAutoCompleteSearch.protot
       if (this.forceStop) {
         return;
       }
       // Sort addresses by timeLastUsed for showing the lastest used address at top.
       records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
 
       let adaptedRecords = handler.getAdaptedProfiles(records, focusedInput);
       let result = null;
-      if (isAddressField) {
-        result = new AddressResult(searchString,
-                                   info.fieldName,
-                                   allFieldNames,
-                                   adaptedRecords,
-                                   {isInputAutofilled});
-      } else {
-        let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
+      let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
 
-        result = new CreditCardResult(searchString,
+      result = new AutocompleteResult(searchString,
                                       info.fieldName,
                                       allFieldNames,
                                       adaptedRecords,
                                       {isSecure, isInputAutofilled});
-      }
       listener.onSearchResult(this, result);
       ProfileAutocomplete.lastProfileAutoCompleteResult = result;
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
    */
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -48,16 +48,21 @@ class ProfileAutoCompleteResult {
     this._allFieldNames = [...this._matchingProfiles.reduce((fieldSet, curProfile) => {
       for (let field of Object.keys(curProfile)) {
         fieldSet.add(field);
       }
 
       return fieldSet;
     }, new Set())].filter(field => allFieldNames.includes(field));
 
+    // Force return success code if the focused field is auto-filled in order
+    // to show clear form button popup.
+    if (isInputAutofilled) {
+      resultCode = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
+    }
     // The result code of this result object.
     if (resultCode) {
       this.searchResult = resultCode;
     } else if (matchingProfiles.length > 0) {
       this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
     } else {
       this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
     }
--- a/browser/extensions/formautofill/test/mochitest/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_clear_form.html
@@ -16,17 +16,17 @@ Form autofill test: clear form button
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
 const MOCK_STORAGE = [{
   organization: "Sesame Street",
-  "street-address": "123 Sesame Street.",
+  "street-address": "2 Harrison St\nline2\nline3",
   tel: "+13453453456",
 }, {
   organization: "Mozilla",
   "street-address": "331 E. Evelyn Avenue",
 }, {
   organization: "Tel org",
   tel: "+12223334444",
 }];
@@ -54,16 +54,25 @@ add_task(async function simple_clear() {
   await triggerPopupAndHoverItem("#organization", 0);
   await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]);
 
   await triggerPopupAndHoverItem("#tel", 0);
   doKey("return");
   checkIsFormCleared();
 });
 
+add_task(async function clear_adapted_record() {
+  await triggerPopupAndHoverItem("#street-address", 0);
+  await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]);
+
+  await triggerPopupAndHoverItem("#street-address", 0);
+  doKey("return");
+  checkIsFormCleared();
+});
+
 add_task(async function clear_modified_form() {
   await triggerPopupAndHoverItem("#organization", 0);
   await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]);
 
   await setInput("#tel", "+1111111111", true);
 
   await triggerPopupAndHoverItem("#street-address", 0);
   doKey("return");
--- a/browser/extensions/pocket/locale/ar/pocket.properties
+++ b/browser/extensions/pocket/locale/ar/pocket.properties
@@ -32,12 +32,12 @@ signinfirefox = لِج بفَيَرفُكس
 signupfirefox = سجّل بفَيَرفُكس
 viewlist = اعرض القائمة
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = احفظ في Pocket
 saveToPocketCmd.label = احفظ الصفحة في Pocket
-saveToPocketCmd.accesskey = ت
-saveLinkToPocketCmd.label = احفظ الوصلة في Pocket
-saveLinkToPocketCmd.accesskey = ص
+saveToPocketCmd.accesskey = ظ
+saveLinkToPocketCmd.label = احفظ الرابط في Pocket
+saveLinkToPocketCmd.accesskey = ف
 pocketMenuitem.label = اعرض قائمة Pocket
--- a/browser/extensions/pocket/locale/bg/pocket.properties
+++ b/browser/extensions/pocket/locale/bg/pocket.properties
@@ -14,20 +14,20 @@ onlylinkssaved = Могат да бъдат запазвани само връзки
 pagenotsaved = Страницата не е запазена
 pageremoved = Страницата е премахната
 pagesaved = Запазена в Pocket
 processingremove = Премахване на страница…
 processingtags = Добавяне на етикети…
 removepage = Премахване на страница
 save = Запазване
 saving = Запазване…
-signupemail = Регистриране с мейл
+signupemail = Регистриране с електронна поща
 signuptosave = Регистрирайте се в Pocket. Безплатно е.
 suggestedtags = Предложени етикети
-tagline = Запазвайте статии и видеота от Firefox и можете да ги преглеждате в Pocket на всяко устройство по всяко време.
+tagline = Запазвайте статии и видео от Firefox и можете да ги преглеждате в Pocket на всяко устройство по всяко време.
 taglinestory_one = Щракнете на бутона на Pocket за запазване на статия, видео или страница от Firefox.
 taglinestory_two = Преглеждайте в Pocket на всяко устройство и по всяко време.
 tagssaved = Етикетите са добавени
 tos = Продължавайки, вие се съгласявате с <a href="%1$S" target="_blank">Условията за ползване</a> и <a href="%2$S" target="_blank">Политиката за поверителност</a> на Pocket
 tryitnow = Опитайте сега
 signinfirefox = Вписване с Firefox
 signupfirefox = Регистриране с Firefox
 viewlist = Преглед на списъка
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/br/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Ouzhpennañ skritelligoù
+alreadyhaveacct = Un arveriad Pocket oc'h endeo ?
+continueff = Kenderc'hel gant Firefox
+errorgeneric = Ur fazi a zo bet en ur enrollañ davet Pocket.
+learnmore = Gouzout hiroc'h
+loginnow = Kennaskañ
+maxtaglength = Bevennet eo ar skritelligoù da 25 arouezenn
+mustbeconnected = Ret eo deoc'h bezañ kennasket d'ar genrouedad evit bezañ gouest da enrollañ davet Pocket. Gwiriekait ho kennask ha klaskit en-dro.
+onlylinkssaved = N'haller enrollañ ereoù nemetken
+pagenotsaved = N'eo ket bet enrollet ar bajenn
+pageremoved = Dilamet ar bajenn
+pagesaved = Enrollet davet Pocket
+processingremove = O tilemel ar bajenn...
+processingtags = Oc'h ouzhpennañ skritelligoù...
+removepage = Dilemel ar bajenn
+save = Enrollañ
+saving = Oc'h enrollañ…
+signupemail = Krouiñ ur gont gant ar chomlec'h postel
+signuptosave = Krouit ur gont Pocket. Digoust eo.
+suggestedtags = Skritelligoù kinniget
+tagline = Enrollit pennadoù ha videoioù adalek Firefox evit gwelet anezho war Pocket war forzh peseurt trevnad, p'ho peus c'hoant.
+taglinestory_one = Klikit war an afell Pocket evit enrollañ ur pennad, video pe pajenn adalek Firefox.
+taglinestory_two = Sellit anezhañ e Pocket war forzh peseurt trevnad, p'ho peus c'hoant.
+tagssaved = Ouzhpennet ur skritellig
+tos = En ur genderc'hel e asantit da <a href="%1$S" target="_blank">Divizoù Arver</a> ha <a href="%2$S" target="_blank">Reolennoù a-fet buhez prevez</a> Pocket
+tryitnow = Amprouit bremañ
+signinfirefox = Kennaskañ gant Firefox
+signupfirefox = Krouiñ ur gont gant Firefox
+viewlist = Gwelout ar roll
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Enrollañ davet Pocket
+saveToPocketCmd.label = Enrollañ ar bajenn davet Pocket
+saveToPocketCmd.accesskey = c
+saveLinkToPocketCmd.label = Enrollañ an ere davet Pocket
+saveLinkToPocketCmd.accesskey = v
+pocketMenuitem.label = Gwelout ar roll Pocket
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/bs/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Dodaj tagove
+alreadyhaveacct = Već ste Pocket korisnik?
+continueff = Nastavi sa Firefoxom
+errorgeneric = Došlo je do greške prilikom spašavanja u Pocket.
+learnmore = Saznajte više
+loginnow = Prijava
+maxtaglength = Tagovi su ograničeni na 25 znakova
+mustbeconnected = Trebate biti konektovani na Internet da biste spasili u Pocket. Molimo da provjerite vašu konekciju i pokušate ponovo.
+onlylinkssaved = Jedino linkovi mogu biti spašeni
+pagenotsaved = Stranica nije spašena
+pageremoved = Stranica uklonjena
+pagesaved = Spašeno u Pocket
+processingremove = Uklanjam stranicu…
+processingtags = Dodajem tagove…
+removepage = Ukloni stranicu
+save = Spasi
+saving = Spašavam…
+signupemail = Registrujte se pomoću emaila
+signuptosave = Registrujte se na Pocket. Besplatan je.
+suggestedtags = Preporučeni tagovi
+tagline = Spasite članke i video klipove iz Firefoxa za pregled u Pocketu na bilo kojem uređaju u bilo koje vrijeme.
+taglinestory_one = Kliknite Pocket dugme da spasite bilo koji članak, video ili stranicu iz Firefoxa.
+taglinestory_two = Pregledajte u Pocketu na bilo kojem uređaju u bilo koje vrijeme.
+tagssaved = Tagovi dodani
+tos = Nastavljanjem, prihvatate Pocketove <a href="%1$S" target="_blank">Uslove korištenja</a> i <a href="%2$S" target="_blank">Policu privatnosti</a>
+tryitnow = Probajte odmah
+signinfirefox = Prijavite se pomoću Firefoxa
+signupfirefox = Registrujte se pomoću Firefoxa
+viewlist = Prikaži listu
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Spasi u Pocket
+saveToPocketCmd.label = Spasi stranicu u Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Spasi link u Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Prikaži Pocket listu
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ca/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Afegeix etiquetes
+alreadyhaveacct = Ja teniu un compte al Pocket?
+continueff = Continua amb el Firefox
+errorgeneric = S'ha produït un error en intentar desar al Pocket.
+learnmore = Més informació
+loginnow = Inicia la sessió
+maxtaglength = Les etiquetes tenen un límit de 25 caràcters
+mustbeconnected = Heu d'estar connectat a Internet per poder desar al Pocket. Comproveu la connexió i torneu-ho a provar.
+onlylinkssaved = Només es poden desar enllaços
+pagenotsaved = No s'ha desat la pàgina
+pageremoved = S'ha eliminat la pàgina
+pagesaved = S'ha desat al Pocket
+processingremove = S'està eliminant la pàgina…
+processingtags = S'estan afegint les etiquetes…
+removepage = Elimina la pàgina
+save = Desa
+saving = S'està desant…
+signupemail = Registre amb correu electrònic
+signuptosave = Registreu-vos al Pocket. És gratuït.
+suggestedtags = Etiquetes recomanades
+tagline = Deseu articles i vídeos des del Firefox per veure'ls al Pocket en qualsevol dispositiu i a qualsevol hora.
+taglinestory_one = Feu clic al botó del Pocket per desar un article, vídeo o pàgina des del Firefox.
+taglinestory_two = Vegeu-lo al Pocket en qualsevol dispositiu, a qualsevol hora.
+tagssaved = Etiquetes afegides
+tos = Si continueu, accepteu les <a href="%1$S" target="_blank">condicions del servei</a> i la <a href="%2$S" target="_blank">política de privadesa</a> del Pocket
+tryitnow = Proveu-ho ara
+signinfirefox = Inicia la sessió amb el Firefox
+signupfirefox = Registre amb el Firefox
+viewlist = Mostra la llista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Desa al Pocket
+saveToPocketCmd.label = Desa la pàgina al Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Desa l'enllaç al Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Mostra la llista del Pocket
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/cak/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Ketz'aqatisäx Taq Etal
+alreadyhaveacct = ¿La at ruwinaq chik ri Pocket?
+continueff = K'a kisamäj na pa Firefox
+errorgeneric = Xuk'ulun pe jun sachoj toq xatojtob'ej yayakon pa Pocket.
+learnmore = Tetamäx ch'aqa' chik
+loginnow = Titikirisäx molojri'ïl
+maxtaglength = Xa xe 25 tz'ib' richin yetz'ib'äx ri taq etal
+mustbeconnected = K'o chi atokinäq pa K'amaya'l richin yatikïr yayakon pa Pocket. Tanik'oj ri awokem richin natojtob'ej chik.
+onlylinkssaved = Xa xe yeyak ri taq ximonel
+pagenotsaved = Man Yakon ta re Ruxaq
+pageremoved = Yujun Ruxaq
+pagesaved = Yakon pa Pocket
+processingremove = Tajin Niyuj Ruxaq…
+processingtags = Tajin yetz'aqatisäx taq etal…
+removepage = Tiyuj Ruxaq
+save = Tiyak
+saving = Tajin niyak…
+signupemail = Tatikirisaj molojri'ïl rik'in taqoya'l
+signuptosave = Tatz'ib'aj ab'i' rik'in Pocket. Majun rajil.
+suggestedtags = Chilab'en taq Etal
+tagline = Ke'ayaka' ri taq rucholna'oj chuqa' taq rusilowachib'äl Firefox richin natz'ët pa Pocket pa jun chik okisaxel, xab'achike ramaj.
+taglinestory_one = Tapitz'a' rupitz'b'al Pocket richin nayäk jun rucholna'oj, rusilowachib'äl o jun ruxaq Firefox.
+taglinestory_two = Titz'et pa Pocket pa jun chik okisaxel, xab'achike ramaj.
+tagssaved = Xetz'aqatisäx Taq Etal
+tos = Toq yatojqan el, nanimaj chi ri Pocket <a href="%1$S" target="_blank">Rojqanem richin Nokisäx</a> chuqa' <a href="%2$S" target="_blank">Ichinan Na'oj</a>
+tryitnow = Tatojtob'ej Wakami
+signinfirefox = Titikirisäx molojri'ïl rik'in Firefox
+signupfirefox = Tatz'ib'aj ab'i' rik'in Firefox
+viewlist = Titz'et Cholb'äl
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Tiyak pa Pocket
+saveToPocketCmd.label = Tiyak Ruxaq pa Pocket 
+saveToPocketCmd.accesskey = T
+saveLinkToPocketCmd.label = Tiyak Ximonel pa Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Titz'et Rucholb'al Pocket
--- a/browser/extensions/pocket/locale/es-CL/pocket.properties
+++ b/browser/extensions/pocket/locale/es-CL/pocket.properties
@@ -2,34 +2,34 @@
 # 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/.
 
 addtags = Añadir etiquetas
 alreadyhaveacct = ¿Ya eres usuario de Pocket?
 continueff = Continuar con Firefox
 errorgeneric = Hubo un error al intentar guardarla en Pocket.
 learnmore = Aprender más
-loginnow = Conectarse
+loginnow = Conéctate
 maxtaglength = Las etiquetas están limitadas a 25 caracteres
 mustbeconnected = Debes estar conectado a Internet para guardar en Pocket. Por favor, revisa tu conexión y vuelve a intentarlo.
 onlylinkssaved = Solo se pueden guardar enlaces
 pagenotsaved = Página no guardada
 pageremoved = Página eliminada
 pagesaved = Guardada en Pocket
 processingremove = Eliminando página…
 processingtags = Añadiendo etiquetas…
 removepage = Eliminar página
 save = Guardar
 saving = Guardando…
 signupemail = Registrarse usando un email
-signuptosave = Registrarse en Pocket. Es gratis.
+signuptosave = Regístrate en Pocket. Es gratis.
 suggestedtags = Etiquetas sugeridas
 tagline = Guarda artículos y videos desde Firefox para verlos en Pocket en cualquier dispositivo y momento.
 taglinestory_one = Aprieta el botón Pocket para guardar cualquier artículo, video o página de Firefox.
-taglinestory_two = Mírala en Pocket en cualquier dispositivo y momento
+taglinestory_two = Mírala en Pocket en cualquier dispositivo y momento.
 tagssaved = Etiquetas añadidas
 tos = Al continuar, aceptas los <a href="%1$S" target="_blank">Términos del servicio</a> y la <a href="%2$S" target="_blank">Política de privacidad</a> de Pocket.
 tryitnow = Probarlo ahora
 signinfirefox = Conectarse con Firefox
 signupfirefox = Registrarse con Firefox
 viewlist = Ver lista
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/eu/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Gehitu etiketak
+alreadyhaveacct = Pocket erabiltzailea zara jada?
+continueff = Jarraitu Firefoxekin
+errorgeneric = Errorea gertatu da Pocket-en gordetzen saiatzean.
+learnmore = Argibide gehiago
+loginnow = Hasi saioa
+maxtaglength = Etiketak 25 karakterera daude mugatuta
+mustbeconnected = Internetera konektatuta egon behar zara Pocket-en gorde ahal izateko. Egiaztatu zure konektagarritasuna eta saiatu berriro.
+onlylinkssaved = Loturak gorde daitezke soilik
+pagenotsaved = Ez da orria gorde
+pageremoved = Orria kenduta
+pagesaved = Pocket-en gordeta
+processingremove = Orria kentzen…
+processingtags = Etiketak gehitzen…
+removepage = Kendu orria
+save = Gorde
+saving = Gordetzen…
+signupemail = Eman izena posta elektronikoa erabiliz
+signuptosave = Eman izena Pocket-en. Doakoa da.
+suggestedtags = Iradokitako etiketak
+tagline = Gorde Firefoxetik artikuluak eta bideoak edozein gailutan Pocket-en ikusteko, noiznahi.
+taglinestory_one = Egin klik Pocket botoian Firefoxetik edozein artikulu, bideo edo orri gordetzeko.
+taglinestory_two = Ikusi edozein gailutan Pocket-en, noiznahi.
+tagssaved = Etiketak gehituta
+tos = Jarraitzearekin bat, Pocket <a href="%1$S" target="_blank">zerbitzuaren baldintzak</a> eta <a href="%2$S" target="_blank">pribatutasun politika</a> onartzen dituzu
+tryitnow = Probatu orain
+signinfirefox = Hasi saioa Firefoxekin
+signupfirefox = Eman izena Firefoxekin
+viewlist = Ikusi zerrenda
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Gorde Pocket-en
+saveToPocketCmd.label = Gorde orria Pocket-en
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Gorde lotura Pocket-en
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ikusi Pocket zerrenda
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/gl/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Engadir etiquetas
+alreadyhaveacct = Xa é usuario de Pocket?
+continueff = Continuar con Firefox
+errorgeneric = Produciuse un erro ao tentar gardar en Pocket.
+learnmore = Máis información
+loginnow = Identificarse
+maxtaglength = As etiquetas está limitadas a 25 caracteres
+mustbeconnected = É necesario estar conectado á Internet para poder gardar en Pocket. Comprobe a súa conexión e tente de novo.
+onlylinkssaved = Só se poden gardar ligazóns
+pagenotsaved = Non se gardou a páxina
+pageremoved = Retirouse a páxina
+pagesaved = Gardouse en Pocket
+processingremove = Retirando páxina…
+processingtags = Engadindo etiquetas…
+removepage = Retirar páxina
+save = Gardar
+saving = Gardando…
+signupemail = Rexístrese co seu correo
+signuptosave = Rexístrese en Pocket. É de balde.
+suggestedtags = Etiquetas suxeridas
+tagline = Garde artigos e vídeos dende Firefox para velos en Pocket en calquera dispositivo, en calquera momento.
+taglinestory_one = Prema no botón Pocket para gardar calquera artigo, vídeo ou páxina dende Firefox.
+taglinestory_two = Véxao en Pocket en calquera dispositivo, en calquera momento.
+tagssaved = Engadíronse as etiquetas
+tos = Se continúa, acepta os <a href="%1$S" target="_blank">termos do servizo</a> e <a href="%2$S" target="_blank">política de privacidade</a> de Pocket
+tryitnow = Próbeo xa
+signinfirefox = Acceda con Firefox
+signupfirefox = Rexístrese con Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Gardar en Pocket
+saveToPocketCmd.label = Gardar páxina en Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Gardar ligazón en Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver a lista de Pocket
--- a/browser/extensions/pocket/locale/hu/pocket.properties
+++ b/browser/extensions/pocket/locale/hu/pocket.properties
@@ -23,17 +23,17 @@ signupemail = Regisztráció e-maillel
 signuptosave = Regisztráljon ingyenesen a Pocketre.
 suggestedtags = Javasolt címkék
 tagline = Mentsen cikkeket és videókat a Firefoxból a Pocketen való megtekintéshez bármely eszközön, bármikor.
 taglinestory_one = Kattintson a Pocket gombra bármely cikk, videó vagy oldal mentéséhez a Firefoxból.
 taglinestory_two = Nézze meg a Pocketen bármely eszközön, bármikor.
 tagssaved = Címkék hozzáadva
 tos = A folytatással elfogadja a Pocket <a href="%1$S" target="_blank">Szolgáltatási feltételeit</a> és az <a href="%2$S" target="_blank">Adatvédelmi nyilatkozatot</a>
 tryitnow = Próbálja ki most
-signinfirefox = Bejelentkezés a Firefoxszal
+signinfirefox = Firefox bejelentkezés
 signupfirefox = Regisztráció a Firefoxszal
 viewlist = Lista megjelenítése
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Mentés a Pocketbe
 saveToPocketCmd.label = Oldal mentése a Pocketbe
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/is/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Bæta við flokkum
+alreadyhaveacct = Ertu þegar Pocket notandi?
+continueff = Halda áfram með Firefox
+errorgeneric = Upp kom villa við að vista á Pocket.
+learnmore = Fræðast meira
+loginnow = Innskráning
+maxtaglength = Tög eru takmörkuð við 25 stafi
+mustbeconnected = Þú verður að vera tengdur netinu til að vista Pocket. Athugaðu tengingu og reyndu aftur.
+onlylinkssaved = Aðeins er hægt að vista tengla
+pagenotsaved = Ekki tókst að vista síðu
+pageremoved = Síða fjarlægð
+pagesaved = Vistað í Pocket
+processingremove = Fjarlægi síðu…
+processingtags = Bæti við flokkum…
+removepage = Fjarlægja síðu
+save = Vista
+saving = Vista…
+signupemail = Skrá inn með tölvupóstfangi
+signuptosave = Skrá sig inn með Pocket. Það er ókeypis.
+suggestedtags = Flokkar sem mælt er með
+tagline = Vista síður og myndbönd frá Firefox til að skoða í Pocket á hvaða tæki sem er, hvenær sem er.
+taglinestory_one = Smelltu á Pocket hnappinn til að vista grein, myndband eða síðu frá Firefox.
+taglinestory_two = Skoðaðu í Pocket á hvaða tæki sem er, hvenær sem er.
+tagssaved = Bætti við flokkum
+tos = Þú ert að samþykkja <a href="%1$S" target="_blank">skilmála</a> hjá Pocket og <a href="%2$S" target="_blank">reglur um persónugögn</a> með því að halda áfram
+tryitnow = Reyndu það núna
+signinfirefox = Skrá sig inn með Firefox
+signupfirefox = Skrá sig með Firefox
+viewlist = Skoða lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Vista í Pocket
+saveToPocketCmd.label = Vista síðu í Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Vista tengil í Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Skoða Pocket lista
--- a/browser/extensions/pocket/locale/jar.mn
+++ b/browser/extensions/pocket/locale/jar.mn
@@ -14,26 +14,27 @@
 #define es_ES es-ES
 #define es_MX es-MX
 #define fy_NL fy-NL
 #define ga_IE ga-IE
 #define gu_IN gu-IN
 #define hi_IN hi-IN
 #define hy_AM hy-AM
 #define nb_NO nb-NO
+#define ne_NP ne-NP
 #define nn_NO nn-NO
 #define pa_IN pa-IN
 #define pt_BR pt-BR
 #define pt_PT pt-PT
 #define sv_SE sv-SE
 #define zh_CN zh-CN
 #define zh_TW zh-TW
 
 [features/firefox@getpocket.com] @AB_CD@.jar:
 % locale pocket @AB_CD@ %locale/@AB_CD@/
   # For locales we support, include the file from the locale's directory in the
   # source tree.
   # For other locales (and en-US) fallback to the en-US directory.
-#if AB_CD == ach || AB_CD == ar || AB_CD == ast || AB_CD == az || AB_CD == be || AB_CD == bg || AB_CD == bn_BD || AB_CD == bn_IN || AB_CD == cs || AB_CD == cy || AB_CD == da || AB_CD == de || AB_CD == dsb || AB_CD == el || AB_CD == en_GB || AB_CD == en_US || AB_CD == eo || AB_CD == es_AR || AB_CD == es_CL || AB_CD == es_ES || AB_CD == es_MX || AB_CD == et || AB_CD == fa || AB_CD == ff || AB_CD == fi || AB_CD == fr || AB_CD == fy_NL || AB_CD == ga_IE || AB_CD == gd || AB_CD == gu_IN || AB_CD == he || AB_CD == hi_IN || AB_CD == hr || AB_CD == hsb || AB_CD == hu || AB_CD == hy_AM || AB_CD == id || AB_CD == it || AB_CD == ja || AB_CD == ka || AB_CD == kab || AB_CD == kk || AB_CD == km || AB_CD == ko || AB_CD == lij || AB_CD == lt || AB_CD == lv || AB_CD == mr || AB_CD == ms || AB_CD == nb_NO || AB_CD == nl || AB_CD == nn_NO || AB_CD == or || AB_CD == pa_IN || AB_CD == pl || AB_CD == pt_BR || AB_CD == pt_PT || AB_CD == rm || AB_CD == ro || AB_CD == ru || AB_CD == sk || AB_CD == sl || AB_CD == sq || AB_CD == sr || AB_CD == sv_SE || AB_CD == te || AB_CD == th || AB_CD == tl || AB_CD == tr || AB_CD == uk || AB_CD == zh_CN || AB_CD == zh_TW
+#if AB_CD == ach || AB_CD == ar || AB_CD == ast || AB_CD == az || AB_CD == be || AB_CD == bg || AB_CD == bn_BD || AB_CD == bn_IN || AB_CD == br || AB_CD == bs || AB_CD == ca || AB_CD == cak || AB_CD == cs || AB_CD == cy || AB_CD == da || AB_CD == de || AB_CD == dsb || AB_CD == el || AB_CD == en_GB || AB_CD == en_US || AB_CD == eo || AB_CD == es_AR || AB_CD == es_CL || AB_CD == es_ES || AB_CD == es_MX || AB_CD == et || AB_CD == eu || AB_CD == fa || AB_CD == ff || AB_CD == fi || AB_CD == fr || AB_CD == fy_NL || AB_CD == ga_IE || AB_CD == gd || AB_CD == gl || AB_CD == gu_IN || AB_CD == he || AB_CD == hi_IN || AB_CD == hr || AB_CD == hsb || AB_CD == hu || AB_CD == hy_AM || AB_CD == id || AB_CD == is || AB_CD == it || AB_CD == ja || AB_CD == ka || AB_CD == kab || AB_CD == kk || AB_CD == km || AB_CD == kn || AB_CD == ko || AB_CD == lij || AB_CD == lt || AB_CD == ltg || AB_CD == lv || AB_CD == mk || AB_CD == ml || AB_CD == mr || AB_CD == ms || AB_CD == my || AB_CD == nb_NO || AB_CD == ne_NP || AB_CD == nl || AB_CD == nn_NO || AB_CD == oc || AB_CD == or || AB_CD == pa_IN || AB_CD == pl || AB_CD == pt_BR || AB_CD == pt_PT || AB_CD == rm || AB_CD == ro || AB_CD == ru || AB_CD == sk || AB_CD == sl || AB_CD == sq || AB_CD == sr || AB_CD == sv_SE || AB_CD == ta || AB_CD == te || AB_CD == th || AB_CD == tl || AB_CD == tr || AB_CD == uk || AB_CD == ur || AB_CD == vi || AB_CD == zh_CN || AB_CD == zh_TW
   locale/@AB_CD@/                (@AB_CD@/*)
 #else
   locale/@AB_CD@/                (en-US/*)
 #endif
--- a/browser/extensions/pocket/locale/ka/pocket.properties
+++ b/browser/extensions/pocket/locale/ka/pocket.properties
@@ -1,43 +1,43 @@
 # 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/.
 
-addtags = იარლიყების დამატება
+addtags = ჭდეების დამატება
 alreadyhaveacct = უკვე იყენებთ Pocket-ს?
 continueff = Firefox-ით გაგრძელება
 errorgeneric = Pocket-ში შენახვისას დაფიქსირდა შეცდომა.
 learnmore = დაწვრილებით
 loginnow = შესვლა
-maxtaglength = იარლიყები შეზღუდულია 25 ასომდე
+maxtaglength = ჭდე უნდა შედგებოდეს არაუმეტეს 25 სიმბოლოსგან
 mustbeconnected = Pocket-ში შესანახად საჭიროა ინტერნეთთან კავშირი. გთხოვთ შეამოწმეთ თქვენი კავშირი და ხელახლა ცადეთ.
 onlylinkssaved = შესაძლებელია მხოლოდ ბმულების შენახვა
 pagenotsaved = გვერდი არ შეინახა
 pageremoved = გვერდი წაიშალა
 pagesaved = შეინახა Pocket-ში
 processingremove = იშლება გვერდი…
-processingtags = ემატება იარლიყები…
+processingtags = ემატება ჭდეები…
 removepage = გვერდის წაშლა
 save = შენახვა
 saving = ინახება…
 signupemail = რეგისტრაცია ელ-ფოსტით
 signuptosave = დარეგისტრირდით Pocket-ზე. ეს უფასოა.
-suggestedtags = შემოთავაზებული იარლიყები
-tagline = შეინახეთ სტატიები და ვიდეობეი Firefox-იდან მათ Pocket-ში სანახავად ნებისმიერ მოწყობილობაზე, ნებისმიერ დროს.
-taglinestory_one = Firefox-იდან ნებისმიერი სტატიის, ვიდეოს ან გვერდის შესანახად დააწკაპეთ Pocket-ის ღილაკს.
+suggestedtags = შემოთავაზებული ჭდეები
+tagline = შეინახეთ სტატიები და ვიდეოები Firefox-იდან მათ Pocket-ში სანახავად ნებისმიერ მოწყობილობაზე, ნებისმიერ დროს.
+taglinestory_one = Firefox-იდან ნებისმიერი სტატიის, ვიდეოს ან გვერდის შესანახად დააწკაპეთ Pocket ღილაკს.
 taglinestory_two = დაათვალიერეთ Pocket-ში ნებისმიერ მოწყობილობაზე, ნებისმიერ დროს.
-tagssaved = იარლიყები დაემატა
-tos = გაგრძელების შემთხვევაში თქვენ ეთანხმებით Pocket-ის <a href="%1$S" target="_blank">მომსახურების პირობებს</a> და <a href="%2$S" target="_blank">პრივატულობის პოლიტიკას</a>
+tagssaved = ჭდეები დამატებულია
+tos = გაგრძელების შემთხვევაში, თქვენ ეთანხმებით Pocket-ის <a href="%1$S" target="_blank">მომსახურების პირობებს</a> და <a href="%2$S" target="_blank">პირადულობის დაცვის დებულებას</a>
 tryitnow = სცადეთ ახლავე
 signinfirefox = შესვლა Firefox-ით
 signupfirefox = რეგისრაცია Firefox-ით
 viewlist = სიის ნახვა
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Pocket-ში შენახვა
 saveToPocketCmd.label = გვერდის შენახვა Pocket-ში
-saveToPocketCmd.accesskey = k
+saveToPocketCmd.accesskey = გ
 saveLinkToPocketCmd.label = ბმულის შენახვა Pocket-ში
-saveLinkToPocketCmd.accesskey = o
+saveLinkToPocketCmd.accesskey = ბ
 pocketMenuitem.label = Pocket სიის ნახვა
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/kn/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = ಟ್ಯಾಗ್‌ಗಳನ್ನು ಸೇರಿಸು
+alreadyhaveacct = ನೀವು ಈಗಾಗಲೇ ಪಾಕೆಟ್ ಬಳಕೆದಾರರೆ?
+continueff = Firefox ಜೊತೆ ಮುಂದುವರೆಯಿರಿ
+errorgeneric = ಪಾಕೆಟ್‌ನಲ್ಲಿ ಉಳಿಸಲು ಪ್ರಯತ್ನಿಸಿದಾಗ ದೋಷ ಉಂಟಾಗಿದೆ.
+learnmore = ಇನ್ನಷ್ಟು ಅರಿತುಕೊಳ್ಳಿ
+loginnow = ಪ್ರವೇಶಿಸು
+maxtaglength = ಟ್ಯಾಗ್‌ಗಳು 25 ಅಕ್ಷರಗಳಿಗೆ ಸೀಮಿತವಾಗಿವೆ
+mustbeconnected = ನೀವು Pocket ನಲ್ಲಿ ಉಳಿಸಲು ಅಂತರ್ಜಾಲಕ್ಕೆ ಸಂಪರ್ಕ ಹೊಂದಿರಬೇಕಾಗುತ್ತದೆ. ದಯವಿಟ್ಟು ಅಂತರಜಾಲಕ್ಕೆ ಸಂಪರ್ಕಿತಗೊಂಡಿದ್ದೀರಿ ಎಂದು ಪರೀಕ್ಷಿಸಿ ನಂತರ ಇನ್ನೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ.
+onlylinkssaved = ಕೇವಲ ಕೊಂಡಿಗಳನ್ನು ಮಾತ್ರ ಉಳಿಸಬಹುದು
+pagenotsaved = ಪುಟವನ್ನು ಉಳಿಸಲಾಗಲಿಲ್ಲ
+pageremoved = ಪುಟವನ್ನು ತೆಗೆಯಲಾಗಿದೆ
+pagesaved = ಪಾಕೆಟ್‌ನಲ್ಲಿ ಉಳಿಸಲಾಗಿದೆ
+processingremove = ಪುಟವನ್ನು ತೆಗೆಯಲಾಗುತ್ತಿದೆ...
+processingtags = ಟ್ಯಾಗ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗುತ್ತಿದೆ...
+removepage = ಪುಟವನ್ನು ತೆಗೆದುಹಾಕು
+save = ಉಳಿಸು
+saving = ಉಳಿಸಲಾಗುತ್ತಿದೆ…‍
+signupemail = ಇಮೇಲ್‌ನಿಂದ ಸೈನ್ ಅಪ್ ಮಾಡಿ
+signuptosave = ಪಾಕೆಟ್‌ಗೆ ಸೈನ್ ಅಪ್ ಆಗಿ. ಇದು ಉಚಿತ.
+suggestedtags = ಸೂಚಿಸಿದ ಟ್ಯಾಗ್‌ಗಳು
+tagline = Firefox ನಿಂದ ಲೇಖನಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಉಳಿಸಿರಿ ಮತ್ತು ಅವನ್ನು ಯಾವುದೇ ಸಾಧನದಲ್ಲಿ, ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ಪಾಕೆಟ್‌ನಿಂದ ನೋಡಿರಿ.
+taglinestory_one = ಯಾವುದೇ ಲೇಖನ, ವೀಡಿಯೋ ಅಥವಾ ಪುಟವನ್ನು Firefox ನಿಂದ ಉಳಿಸಲು ಪಾಕೆಟ್ ಬಟನ್ ಮೇಲೆ ಕ್ಲಿಕ್ ಮಾಡಿ.
+taglinestory_two = ಯಾವದೇ ಸಾಧನದಿಂದ, ಯಾವುದೇ ಸಮಯದಲಿ ಪಾಕೆಟ್‌ನಲ್ಲಿ ನೋಡಿರಿ.
+tagssaved = ಸೇರಿಸಿದ ಟ್ಯಾಗ್‌ಗಳು‍
+tos = ಮುಂದುವರೆಯುವುದರಿಂದ, ನೀವು Pocket ನ <a href="%1$S" target="_blank">‍‍ಸೇವಾ ನಿಯಮಗಳು</a> ಮತ್ತು <a href="%2$S" target="_blank">ಗೌಪ್ಯತಾ ನೀತಿ</a> ಒಪ್ಪುತ್ತೀರಿ‍‍
+tryitnow = ಈಗ ಪ್ರಯತ್ನಿಸು
+signinfirefox = Firefox ಜೊತೆ ಸೈನ್ ಇನ್ ಆಗಿ
+signupfirefox = Firefox ಜೊತೆ ಸೈನ್ ಅಪ್ ಆಗಿ
+viewlist = ಪಟ್ಟಿಯನ್ನು ನೋಡಿ
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = ಪಾಕೆಟ್
+pocket-button.tooltiptext = ಪಾಕೆಟ್‌ನಲ್ಲಿ ಉಳಿಸಿ‍
+saveToPocketCmd.label = Pocketಗೆ ಪುಟವನ್ನು ಉಳಿಸಿ
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = ಕೊಂಡಿಯನ್ನು ಪಾಕೆಟ್‌ಗೆ ಉಳಿಸಿ
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = ಪಾಕೆಟ್ ಪಟ್ಟಿಯನ್ನು ನೋಡಿ
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ltg/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Davīnōt birkas
+alreadyhaveacct = Jau esi Pocket lītōtōjs?
+continueff = Turpynōt ar Firefox
+errorgeneric = Klaida saglobojūt Pocket.
+learnmore = Vaira
+loginnow = Pīsazaceit
+maxtaglength = Birkas navar byut garōkas par 25 simbolim
+mustbeconnected = Kab saglobōt Pocket, jōbyun savīnotam ar Internetu. Lyudzu porbaudi savīnojumu un paraugi vāļ reizi.
+onlylinkssaved = Var saglobōt viņ saites
+pagenotsaved = Lopa nav saglobōta
+pageremoved = Lopa nūjimta
+pagesaved = Saglobōts Pocket
+processingremove = Izjem lopu…
+processingtags = Davīnoj birkas…
+removepage = Izjimt lopu
+save = Saglobōt
+saving = Sagloboj…
+signupemail = Pīzarakstēt ar e-postu
+signuptosave = Pīzariģistrēt Pocket. Tys ir par darma.
+suggestedtags = Īsaceitōs birkas
+tagline = Sagloboj rokstus voi video nu Firefox, kab vērtīs ar Pocket jebkurā īreicē un laikā.
+taglinestory_one = Damīdz Pocket pūgu, kab saglobōt Firefox rokstus, video voi lopys.
+taglinestory_two = Verīs Pocket uz jebkuras īreices jebkurā laikā.
+tagssaved = Birkas pīvīnōtas
+tos = Turpynojūt, tu piekreiti Pocket <a href="%1$S" target="_blank">Nūteikumim</a> un <a href="%2$S" target="_blank">Privātuma politikai</a>
+tryitnow = Paraugi parreizi
+signinfirefox = Pīzaslēgt ar Firefox kontu
+signupfirefox = Pīzareģistrēt ar Firefox kontu
+viewlist = Rōdēt sarokstu
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Saglobōt Pocket
+saveToPocketCmd.label = Saglobōt lopu Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Saglobōt saiti Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Rōdēt Pocket sarokstu
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/mk/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Додај етикети
+alreadyhaveacct = Веќе користите Pocket?
+continueff = Продолжи со Firefox
+errorgeneric = Се појави грешка кога се обидов да зачувам на Pocket.
+learnmore = Дознајте повеќе
+loginnow = Најавете се
+maxtaglength = Етикетите се ограничени на 25 знаци
+mustbeconnected = Мора да сте поврзани на Интернет за да зачувате во Pocket. Ве молам проверете ја Вашата врска и обидете се повторно.
+onlylinkssaved = Може да се зачувуваат само врски до мрежни места
+pagenotsaved = Страницата не е зачувана
+pageremoved = Страницата е отстранета
+pagesaved = Зачувано во Pocket
+processingremove = Страницата се отстранува…
+processingtags = Додавам етикети …
+removepage = Отстрани страница
+save = Сними
+saving = Се снима…
+signupemail = Регистрирајте се со е-пошта
+signuptosave = Регистрирајте се на Pocket. Бесплатно е.
+suggestedtags = Предложени етикети
+tagline = Зачувајте написи и видеа од Firefox за преглед во Pocket на било кој уред, во било кое време.
+taglinestory_one = Кликнете на копчето Pocket за да зачувате напис, видео или страница од Firefox.
+taglinestory_two = Прегледајте во Pocket на било кој уред, во било кое време.
+tagssaved = Додадени етикети
+tos = Доколку продолжите, се согласувате со <a href="%1$S" target="_blank">Условите за користење</a> и <a href="%2$S" target="_blank">Политиката за приватност</a> на Pocket
+tryitnow = Пробајте го сега
+signinfirefox = Најавете се со Firefox
+signupfirefox = Регистрирајте се со Firefox
+viewlist = Види листа
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Зачувај во Pocket
+saveToPocketCmd.label = Зачувај страница во Pocket
+saveToPocketCmd.accesskey = к
+saveLinkToPocketCmd.label = Зачувај врска во Pocket
+saveLinkToPocketCmd.accesskey = о
+pocketMenuitem.label = Види листа на Pocket
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ml/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = ടാഗുകള്‍ ചേര്‍ക്കുക
+alreadyhaveacct = നിലവില്‍ പോക്കറ്റ് ഉപയോക്താവാണോ?
+continueff = ഫയര്‍ഫോക്സില്‍ തുടരുക
+errorgeneric = 'പോക്കറ്റ്' ലേക്ക് സംരക്ഷിക്കാൻ ശ്രമിക്കുമ്പോൾ ഒരു പിശക് സംഭവിച്ചു.
+learnmore = കൂടുതലറിയാം
+loginnow = പ്രവേശിക്കുക
+maxtaglength = ടാഗുകള്‍ 25 അക്ഷരങ്ങളിലേക്ക് പരിമിതപ്പെടുത്തിയിരിക്കുന്നു
+mustbeconnected = പോക്കറ്റിലേക്ക് സേവ് ചെയ്യാൻ നിങ്ങൾ ഇന്റർനെറ്റിൽ കണക്റ്റു ചെയ്തിരിക്കണം. നിങ്ങളുടെ കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.
+onlylinkssaved = കണ്ണികള്‍ മാത്രമേ സൂക്ഷിക്കാന്‍ പറ്റു
+pagenotsaved = പേജ് സംരക്ഷിച്ചില്ല
+pageremoved = താള്‍ നീക്കം ചെയ്തു
+pagesaved = ' പോക്കറ്റിലേക്ക് 'സംരക്ഷിച്ചു
+processingremove = താള്‍ കളയുന്നു…
+processingtags = ടാഗ് ചേര്‍ക്കുന്നു…
+removepage = താള്‍ നീക്കം ചെയ്യുക
+save = സൂക്ഷിക്കുക
+saving = സൂക്ഷിയ്ക്കുന്നു..
+signupemail = ഇമെയിൽ കൊണ്ട് പങ്ക് ചേരുക
+signuptosave = പോക്കറ്റിൽ പങ്കുചേരുക. തികച്ചും സൗജന്യമായി.
+suggestedtags = നിര്‍ദ്ദേശിച്ച ടാഗുകള്‍
+tagline = ഫയർഫോക്സിൽ നിന്ന് പോക്കറ്റിൽ ഏത് ഉപായത്തിലും, ഏത് സമയത്തും ലേഖനങ്ങളും വീഡിയോകളും സംരക്ഷിച്ച് കാണാം.
+taglinestory_one = ഫയർഫോക്സിൽ നിന്ന് ഏതെങ്കിലും ലേഖനം, വീഡിയോ അല്ലെങ്കിൽ താള് പോക്കറ്റ് ബട്ടൺ അടിച്ച് സംരക്ഷിക്കാം.
+taglinestory_two = പോക്കറ്റിൽ ഏത് ഉപായത്തിലും, ഏത് സമയത്തും കാണുക.
+tagssaved = ടാഗുകൾ ചേർത്തു
+tos = തുടരുന്നതിലൂടെ, നിങ്ങൾ പോക്കറ്റിന്റെ <a href="%1$S" target="_blank">സേവന നിബന്ധനകളോടും</a><a href="%2$S" target="_blank">സ്വകാര്യ വ്യവസ്ഥകളോടും</a> സമ്മതിക്കുന്നു
+tryitnow = ഇപ്പോൾ പരീക്ഷിക്കുക
+signinfirefox = ഫയർഫോക്സിൽ പ്രവേശിയ്ക്കുക
+signupfirefox = ഫയർഫോക്സിൽ പങ്ക് ചേരുക
+viewlist = പട്ടിക കാണുക
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = പോക്കറ്റ്
+pocket-button.tooltiptext = 'പോക്കറ്റിലേക്ക്' സംരക്ഷിച്ചു
+saveToPocketCmd.label = 'പോക്കറ്റിലേക്ക്' താള്‍ സംരക്ഷിക്കുക
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = 'പോക്കറ്റിലേക്ക്' ലിങ്ക് സംരക്ഷിക്കുക
+saveLinkToPocketCmd.accesskey = ഓ
+pocketMenuitem.label = പോക്കറ്റ് പട്ടിക കാണുക
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/my/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = စာအမှတ်များ ထည့်ပါ
+alreadyhaveacct = ပေါ့ကတ် အကောင့်ရှိပြီးပြီလား။
+continueff = မီးမြေခွေးကို အသုံးပြုပြီး ဆက်လက်လုပ်ဆောင်မည်
+errorgeneric = ပေါ့ကတ်သို့ သိမ်းရန်ကြိုးစားရာတွင် ပြသာနာတစ်ခု တွေ့ရှိခဲ့သည်။
+learnmore = ဆက်လက်လေ့လာ
+loginnow = ဝင်ပါ
+maxtaglength = စာမှတ်များ၏ စာလုံးရေကန့်သတ်ချက်သည် ၂၅ လုံးဖြစ်သည်
+mustbeconnected = ပေါ့ကတ်သို့သိမ်းဆည်းရန်အတွက် သင်အင်တာနက်သို့ ချိတ်ဆက်ထားရမည်ဖြစ်သည်။ ကျေးဇူးပြု၍ သင့် အင်တာနက်ကို ချိတ်ဆက်ပြီး ထပ်မံကြိုးစားကြည့်ပါ။
+onlylinkssaved = လင်ခ့်များကိုသာ သိမ်းထားနိုင်သည်
+pagenotsaved = စာမျက်နှာကို မသိမ်းလိုက်ရပါ
+pageremoved = စာမျက်နှာကို ဖယ်ရှားပြီး
+pagesaved = ပေါ့ကတ်သို့သိမ်းပြီး
+processingremove = စာမျက်နှာကို ဖယ်ရှားနေသည်…
+processingtags = စာမှတ်များကို ထည့်နေသည်...
+removepage = စာမျက်နှာကို ဖျက်ရန်
+save = သိမ်းရန်
+saving = သိမ်းဆည်းနေသည်…
+signupemail = အီးမေးလ်ဖြင့် မှတ်ပုံတင်ပါ
+signuptosave = ပေါ့ကတ်အတွက် မှတ်ပုံတင်ပါ။ အခမဲ့ဖြစ်ပါသည်။
+suggestedtags = အကြံပေးထားသော စာတိုများ
+tagline = မည်သည့်ကိရိယာ၊ မည်သည့်အချိန်တွင်မဆို ပေါ့ကတ်ထဲတွင် ကြည့်ရန် မီးမြေခွေးမှ စာစုများနှင့် ဗွီဒီယိုများကို သိမ်းပါ။
+taglinestory_one = မီးမြေခွေးမှ မည်သည့်စာစု၊ ဗွီဒီယို သို့မဟုတ် စာမျက်နှာကို သိမ်းရန် ပေါ့ကတ်ခလုတ်ကို နှိပ်ပါ။
+taglinestory_two = မည်သည့် ကိရိယာ၊ မည်သည့် အချိန်တွင်မဆို ပေါ့ကတ်ထဲတွင် ကြည့်ပါ။
+tagssaved = စာမှတ်များ ထည့်ပြီး
+tos = ဆက်လက်ဆောင်ရွက်ခြင်းအားဖြင့် သင်သည် Pocket ရဲ့<a href="%1$S" target="_blank">စည်းမျဉ်းစည်းကမ်းများ</a>နှင့်<a href="%2$S" target="_blank">ကိုယ်ရေးဆိုင်ရာမူဝါဒ</a>ကိုသ​ဘောတူပါသည်
+tryitnow = ယခုပင် စမ်းကြည့်ပါ
+signinfirefox = မီးမြေခွေးအကောင့်ဖြင့် ဝင်ရောက်ပါ
+signupfirefox = မီးမြေခွေးအကောင့်ဖြင့် မှတ်ပုံတင်ပါ
+viewlist = စာရင်းကို ကြည့်ရန်
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket သို့ သိမ်းရန်
+saveToPocketCmd.label = စာမျက်နှာကို Pocket ထဲသို့ သိမ်းပါ
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = လင့်ခ်ကို Pocket ထဲသို့(o) သိမ်းပါ
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket စာရင်းကို ကြည့်ရန်
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ne-NP/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = ट्याग थप्नुहोस्
+alreadyhaveacct = के तपाईँ पकेट प्रयोगकर्ता हो?
+continueff = Firefox सँग जारी राख्नुहोस्
+errorgeneric = Pocket मा सङ्ग्रह गर्न प्रयास गर्दा, एउटा त्रुटी भयो।
+learnmore = अझ जान्नुहोस्
+loginnow = लग इन
+maxtaglength = ट्याग २५ अक्षर सम्म सिमित हुन्छन
+mustbeconnected = तपाईँ Pocket मा सङ्ग्रह गर्न इन्टरनेटसँग जोडिएको हुनुपर्छ। आफ्नो जडान जाँच र फेरि प्रयास गर्नुहोस्।
+onlylinkssaved = लिङ्कहरू मात्र सङ्ग्रह गर्न सकिन्छ
+pagenotsaved = पृष्ठ सङ्ग्रह गरिएको छैन
+pageremoved = पृष्ठ हटाइयो
+pagesaved = Pocket मा सङ्ग्रह गरियो
+processingremove = पृष्ठ हटाउँदै ...
+processingtags = ट्यागहरू थप्दै…
+removepage = पृष्ठ हटाउनुहोस्
+save = सङ्ग्रह गर्नुहोस्
+saving = सङ्ग्रह गरिँदै…
+signupemail = इमेल प्रयोग गरेर साइन अप गर्नुहोस्
+signuptosave = Pocket मा साइन अप गर्नुहोस् । यो निःशुल्क छ ।\u0020
+suggestedtags = सिफारिस गरिएका ट्यागहरू
+tagline = कुनै पनि उपकरणमा, कुनै पनि समयमा Pocket हेर्न Firefox बाट लेख र भिडियो सङ्ग्रह गर्नुहोस्।
+taglinestory_one = Firefox बाट कुनै पनि लेख, भिडियो वा पृष्ठ सङ्ग्रह गर्न Pocket Button थिच्नुहोस्।
+taglinestory_two = कुनै पनि उपकरण, कुनै पनि समय Pocket मा हेर्नुहोस्।
+tagssaved = ट्यागहरू थिपियो
+tos = अघि बढेर, तपाईँ Pocket को <a href="%1$S" target="_blank">सेवा सर्तहरू</a> र <a href="%2$S" target="_blank">गोपनीयता नीति</a> स्विकार्दै हुनुहुन्छ
+tryitnow = अहिले नै परीक्षण गर्नुहोस्
+signinfirefox = Firefox प्रयोग गरेर साइन इन गर्नुहोस्
+signupfirefox = Firefox प्रयोग गरेर साइन अप गर्नुहोस्
+viewlist = सुची हेर्नुहोस्
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket मा सङ्ग्र गर्नुहोस्
+saveToPocketCmd.label = पृष्ठलाई Pocket मा सङ्ग्रह गर्नुहोस्
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Pocket मा लिङ्क सङ्ग्रह गर्नुहोस्
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Poket को सुची हेर्नुहोस्
--- a/browser/extensions/pocket/locale/nn-NO/pocket.properties
+++ b/browser/extensions/pocket/locale/nn-NO/pocket.properties
@@ -1,36 +1,36 @@
 # 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/.
 
-addtags = Legg til merkelapp-stikkord
+addtags = Legg til etikettar
 alreadyhaveacct = Allereie ein Pocket-brukar?
 continueff = Hald fram med Firefox
 errorgeneric = Eit problem oppstod ved lagring til Pocket.
 learnmore = Les meir
 loginnow = Logg inn
-maxtaglength = Merkelapp-stikkord er avgrensa til 25 teikn
+maxtaglength = Etikettar er avgrensa til 25 teikn
 mustbeconnected = Du må vera kopla til nettet for å lagra til Pocket. Kontroller tilkoplinga og prøv igjen.
 onlylinkssaved = Berre lenker kan lagrast
 pagenotsaved = Sida ikkje lagra
 pageremoved = Sida fjerna
 pagesaved = Lagrar til Pocket
 processingremove = Fjernar sida …
-processingtags = Legg til merkelapp-stikkord…
+processingtags = Legg til etikettar…
 removepage = Fjern sida
 save = Lagra
 saving = Lagrar …
 signupemail = Logg inn med e-postadresse
 signuptosave = Registrer deg på Pocket. Det er gratis.
-suggestedtags = Føreslåtte merkelapp-stikkord
+suggestedtags = Føreslåtte etikettar
 tagline = Lagra artiklar og videoar frå Firefox for å visa dei i Pocket på kva som helst eining, når som helst.
-taglinestory_one = Trykk på Pocket-knappen for å lagra kva som helst artikkel, video eller side frå Firefox.
+taglinestory_one = Trykk på Pocket-knappen for å lagra artiklar, videoar eller sider frå Firefox.
 taglinestory_two = Vis i Pocket, på kva som helst eining, når som helst.
-tagssaved = Merkelapp-stikkord lagt til
+tagssaved = Etikettar lagt til
 tos = Ved å fortsetta godtek du Pocket sine <a href="%1$S" target="_blank">tenestevilkår</a> og <a href="%2$S" target="_blank">personvernpraksis</a>
 tryitnow = Prøv no
 signinfirefox = Logg inn med Firefox
 signupfirefox = Registrer deg med Firefox
 viewlist = Vis liste
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/oc/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Apondre d'etiquetas
+alreadyhaveacct = Avètz ja un compte dins Pocket ?
+continueff = Contunhar amb Firefox
+errorgeneric = Una error s'es produita en ensajant d'enregistrar dins Pocket.
+learnmore = Ne saber mai
+loginnow = Dobrir una session
+maxtaglength = Las etiquetas an un limit de 25 caractèrs
+mustbeconnected = Vos cal èsser connectat a Internet per salvar de ligams a Pocket. Verificatz vòstre connexion e tornatz ensajar.
+onlylinkssaved = Se pòdon pas enregistrar que los ligams
+pagenotsaved = La pagina es pas estada enregistrada
+pageremoved = La pagina es estada suprimida
+pagesaved = Es estat enregistrat dins Pocket
+processingremove = Supression de la pagina...
+processingtags = Apondon de las etiquetas...
+removepage = Suprimir la pagina
+save = Enregistrar
+saving = Enregistrament...
+signupemail = S'inscriure amb lo corrièr electronic
+signuptosave = Inscrivètz-vos a Pocket. Es gratuit.
+suggestedtags = Etiquetas recomandadas
+tagline = Salvatz d'articles e vidèos de Firefox per los veire en Pocket de quin que siá periferic quand volatz.
+taglinestory_one = Clicatz lo boton Pocket per salvar d'articles, vidèos o paginas de Firefox.
+taglinestory_two = Vejatz en Pocket de quin que siá periferic quand volatz.
+tagssaved = Etiquetas apondudas
+tos = Per contunhar, vos cal acceptar las <a href="%1$S" target="_blank">condicions de servici</a> e <a href="%2$S" target="_blank">politicas de confidencialitat</a> de Pocket
+tryitnow = Ensajatz-lo ara
+signinfirefox = Connectatz-vos amb Firefox
+signupfirefox = Se marcar amb Firefox
+viewlist = Afichar la lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Es estat enregistrat dins Pocket
+saveToPocketCmd.label = Salvar pagina dins Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Salvar ligam dins Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Mostrar la lista de Pocket
--- a/browser/extensions/pocket/locale/pt-PT/pocket.properties
+++ b/browser/extensions/pocket/locale/pt-PT/pocket.properties
@@ -23,18 +23,18 @@ signupemail = Registar com email
 signuptosave = Registe-se no Pocket. É gratuito.
 suggestedtags = Etiquetas sugeridas
 tagline = Guardar artigos e vídeos do Firefox para os ver no Pocket em qualquer dispositivo, em qualquer altura.
 taglinestory_one = Clique no botão Pocket para guardar qualquer artigo, vídeo ou página a partir Firefox.
 taglinestory_two = Ver no Pocket em qualquer dispositivo, a qualquer altura.
 tagssaved = Etiquetas adicionadas
 tos = Ao continuar, concorda com os <a href="%1$S" target="_blank">termos do serviço</a> e <a href="%2$S" target="_blank">política de privacidade</a> do Pocket
 tryitnow = Experimente-o agora
-signinfirefox = Iniciar sessão com Firefox
-signupfirefox = Registar com Firefox
+signinfirefox = Iniciar sessão com o Firefox
+signupfirefox = Registar com o Firefox
 viewlist = Ver lista
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Guardar no Pocket
 saveToPocketCmd.label = Guardar página no Pocket
 saveToPocketCmd.accesskey = k
--- a/browser/extensions/pocket/locale/sv-SE/pocket.properties
+++ b/browser/extensions/pocket/locale/sv-SE/pocket.properties
@@ -33,11 +33,11 @@ signupfirefox = Registrera dig med Firef
 viewlist = Visa lista
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Spara till Pocket
 saveToPocketCmd.label = Spara sida till Pocket
 saveToPocketCmd.accesskey = k
-saveLinkToPocketCmd.label = Spara Länk till Pocket
-saveLinkToPocketCmd.accesskey = l
+saveLinkToPocketCmd.label = Spara länk till Pocket
+saveLinkToPocketCmd.accesskey = o
 pocketMenuitem.label = Visa Pocket Lista\u0020
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ta/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = குறிகளைச் சேர்
+alreadyhaveacct = ஏற்கனவே கணக்கு இருக்கா?
+continueff = பயர்பாக்ஸ் உடன் தொடரவும்
+errorgeneric = பாக்கெட்டில் சேமிக்கும்போது பிழை ஏற்பட்டது.
+learnmore = மேலும் அறிக
+loginnow = உள் நுழை
+maxtaglength = குறிகள் 25 எழுத்துக்குமேல் இருக்கக்கூடாது
+mustbeconnected = தங்களது பாகெட்டில் சேமிக்க நீங்கள் இணையத் தொடர்பில் இருக்க வேண்டும். தங்களது தொடர்பைச் சரிபார்த்துவிட்டு மீண்டும் முயற்சி செய்யவும்.
+onlylinkssaved = தொடுப்புகள் மட்டுமே சேமிக்க முடியும்
+pagenotsaved = சேமிக்கப்படவில்லை
+pageremoved = பக்கம் நீக்கப்பட்டது
+pagesaved = பாக்கெட்டில் சேமத்தது
+processingremove = பக்கம் நீக்கப்படுகிறது…
+processingtags = குறிச்சொற்கள் சேர்க்கப்படுகின்றன…
+removepage = பக்கத்தை நீக்கு
+save = சேமி
+saving = சேமிக்கிறது…
+signupemail = மின்னஞ்சல் கொண்டு உள்நுழையுங்கள்
+signuptosave = பாக்கெட்டில் புகுப்பதிக. இது இலவசமே.
+suggestedtags = பரிந்துரைக்கப்பட்ட குறிகள்
+tagline = பயர்பாக்ஸ் உலாவியின் மூலம் கட்டுரைகள் மற்றும் காணொளிகளை பாகெட்டில் சேமித்து எந்நேரத்தில் எக்கருவில் இருந்தும் பாருங்கள்.
+taglinestory_one = பாக்கெட் பொத்தானை அழுத்தி எந்த கட்டுரையையும் பக்கத்தையும் பயர்பாக்ஸிலிருந்து சேமிக்க முடியும்.
+taglinestory_two = பாக்கெட்டை எங்கிருந்தும் எந்நேரத்திலும் பார்க்கலாம்.
+tagssaved = குறிகள் சேர்க்கப்பட்டன
+tos = தொடர்வதன் மூலம், நீங்கள் பாக்கெட்டின் <a href="%1$S" target="_blank">சேவை நிபந்தனைகள்</a> மற்றும்<a href="%2$S" target="_blank">தனியுரிம கொள்கைகளையும்</a> ஏற்கிறீர்கள்
+tryitnow = இப்போது முயற்சி
+signinfirefox = பயர்பாக்ஸ் கொண்டு உள்நுழை
+signupfirefox = பயர்பாக்ஸ் கொண்டு உள்நுழை
+viewlist = பட்டியலைப் பார்
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = பாக்கெட்
+pocket-button.tooltiptext = பாக்கெட்டில் சேமி
+saveToPocketCmd.label = பாக்கெட்டில் சேமி
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = தொடுப்பை பாக்கெட்டில் சேமி
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = பாக்கெட் பட்டியலைப் பார்
--- a/browser/extensions/pocket/locale/te/pocket.properties
+++ b/browser/extensions/pocket/locale/te/pocket.properties
@@ -1,17 +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/.
 
 addtags = ట్యాగులను చేర్చు
 alreadyhaveacct = ఇప్పటికే ఒక పాకెట్ వాడుకరా?
 continueff = Firefoxతో కొనసాగించండి
 errorgeneric = పాకెట్ కు సేవ్ చేయడానికి ప్రయత్నిస్తున్నప్పుడు లోపం ఉంది.
-learnmore = మరింత తెలుసుకోండి
+learnmore = ఇంకా తెలుసుకోండి
 loginnow = లాగ్ ఇన్
 maxtaglength = టాగ్లు 25 అక్షరాలకు పరిమితం చేయబడ్డాయి
 mustbeconnected = మీరు పాకెట్ కు సేవ్ చేయడానికి ఇంటర్నెట్ కనెక్ట్ చేయక తప్పదు. మీ కనెక్షన్ను తనిఖీ చేసి, మళ్ళీ ప్రయత్నించండి.
 onlylinkssaved = కేవలం లింకులు సేవ్ చేయవచ్చు
 pagenotsaved = పేజీ సేవ్ చేయబడలేదు
 pageremoved = పేజీ తీసివేయబడెను
 pagesaved = పాకెట్ కు సేవ్ చేయబడింది
 processingremove = పేజీని తొలగించు…
--- a/browser/extensions/pocket/locale/th/pocket.properties
+++ b/browser/extensions/pocket/locale/th/pocket.properties
@@ -24,17 +24,17 @@ signuptosave = ไม่มีค่าใช้จ่ายในการลงทะเบียน Pocket
 suggestedtags = ป้ายกำกับที่ถูกแนะนำ
 tagline = บันทึกบทความและวิดีโอจาก Firefox เพื่อดูใน Pocket บนอุปกรณ์ต่าง ๆ เวลาไหนก็ได้
 taglinestory_one = คลิกปุ่ม Pocket เพื่อบันทึกบทความ วิดีโอ หรือหน้าจาก Firefox
 taglinestory_two = ดูใน Pocket บนอุปกรณ์ต่าง ๆ เวลาไหนก็ได้
 tagssaved = ป้ายกำกับถูกเพิ่มแล้ว
 tos = หากตกลง หมายความว่า คุณยอมรับ<a href="%1$S" target="_blank">เงื่อนไขการให้บริการ</a> และ<a href="%2$S" target="_blank">นโยบายความเป็นส่วนตัว</a>ของ Pocket
 tryitnow = ลองเลย
 signinfirefox = ลงชื่อเข้าด้วย Firefox
-signupfirefox = ลงทะเบียนกับ Firefox
+signupfirefox = ลงทะเบียนด้วย Firefox
 viewlist = ดูรายการ
 
 # LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
 # "Pocket" is a brand name.
 pocket-button.label = Pocket
 pocket-button.tooltiptext = บันทึกไปยัง Pocket
 saveToPocketCmd.label = บันทึกหน้าไปยัง Pocket
 saveToPocketCmd.accesskey = k
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/ur/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = ٹيگز کا اضافہ کريں
+alreadyhaveacct = پہلے سے ہی Pocket صارف ہیں؟
+continueff = Firefox کے ساتھ جاری رکھیں
+errorgeneric = جب Pocket میں محفوظ کرنے کی کوشش کی گئی تو ایک نقص پایا گیا۔
+learnmore = مزید سیکھیں
+loginnow = لاگ ان
+maxtaglength = ٹیگز 25 حروف تک محدود ہیں
+mustbeconnected = آپکو Pocket میں محفوظ کرنے کے لیئے انٹرنیٹ سے جڑنا ظروری ہے۔ براہ مہربانی اپنے انٹرنیٹ کنکشن کی بڑتال کریں اور دوبارہ کوشش کریں۔
+onlylinkssaved = صرف ربط محفوط ہو سکتے ہیں
+pagenotsaved = صفحہ محفوظ نہیں ہوا
+pageremoved = صفحہ ہٹا دیا گیا
+pagesaved = Pocket میں محفوظ شدہ
+processingremove = صفحہ ہٹا رہے ہیں…
+processingtags = ٹیگز اضافہ کر رہے ہیں...
+removepage = صفحہ ہٹائیں
+save = محفوظ
+saving = محفوظ کر رہا ہے…
+signupemail = ای میل کے ساتھ سائن اپ کریں
+signuptosave = Pocket کے لیئے سائن اپ کریں۔ یہ بلکل مفت ہے۔
+suggestedtags = تجویز شدہ ٹیگز
+tagline = Firefox  سے مظامین اور وڈیوذ کو محفوظ کریں تاکہ Pocket  میں کسی بھی آلہ پر کسی بھی وقت نظارہ کر سکیں
+taglinestory_one = Firefox سے کسی بھی مظمون، وڈیو کو محفوظ کرنے کے لیئے Pocket کے بٹن پر کلک کریں۔
+taglinestory_two = Pocket میں نظارہ کریں کسی بھی آلہ پر، کس بھی وقت۔
+tagssaved = ٹیگز کا اظافہ کر دیا گیا
+tos = جاری رکھتے ہوئے، آپ Pocket کی <a href="%1$S" target="_blank">شرائط و ضوابط</a> اور <a href="%2$S" target="_blank">راز داری کی پالیسی</a> سے متفق ہیں
+tryitnow = ابھی آزمائیں
+signinfirefox = Firefox کے ساتھ سائن ان کریں
+signupfirefox = Firefox کے ساتھ سائن اپ کریں
+viewlist = فہرست کا نظارہ کریں
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket میں محفوظ کریں
+saveToPocketCmd.label = Pocket میں صفحات محوظ کریں
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = ربط کو Pocket میں محفوظ کرِیں
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket فہرست کا نظارہ کریں
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pocket/locale/vi/pocket.properties
@@ -0,0 +1,43 @@
+# 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/.
+
+addtags = Thêm các tag
+alreadyhaveacct = Bạn đã có tài khoản Pocket?
+continueff = Tiếp tục với Firefox
+errorgeneric = Đã xảy ra lỗi khi đang lưu vào Pocket.
+learnmore = Tìm hiểu thêm
+loginnow = Đăng nhập
+maxtaglength = Các tag được giới hạn trong 25 ký tự
+mustbeconnected = Bạn phải kết nối đến Internet để lưu vào Pocket. Xin hãy kiểm tra kết nối của bạn và thử lại.
+onlylinkssaved = Chỉ các các liên kết mới có thể được lưu lại
+pagenotsaved = Trang chưa được lưu
+pageremoved = Trang đã bị xóa
+pagesaved = Đã được lưu vào Pocket
+processingremove = Đang xóa trang...
+processingtags = Đang thêm các tag...
+removepage = Xóa trang
+save = Lưu lại
+saving = Đang lưu lại...
+signupemail = Đăng ký bằng email
+signuptosave = Đăng ký Pocket. Hoàn toàn miễn phí.
+suggestedtags = Các tag được đề xuất
+tagline = Lưu bài viết và video từ Firefox để xem trên Pocket trên bất kỳ thiết bị nào, bất cứ lúc nào.
+taglinestory_one = Nhấp vào nút Pocket để lưu bất kỳ bài viết, video hoặc trang nào từ Firefox.
+taglinestory_two = Xem trên Pocket ở bất cứ thiết bị và thời gian nào.
+tagssaved = Các tag đã được thêm vào
+tos = Nếu tiếp tục, bạn đã đồng với <a href="%1$S" target="_blank">Điều khoản dịch vụ</a> và <a href="%2$S" target="_blank">Chính sách bảo mật</a> của Pocket
+tryitnow = Thử nó ngay
+signinfirefox = Đăng nhập với Firefox
+signupfirefox = Đăng ký với Firefox
+viewlist = Xem danh sách
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Lưu vào Pocket
+saveToPocketCmd.label = Lưu trang vào Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Lưu liên kết vào Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Xem danh sách Pocket
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -84,16 +84,17 @@ ifndef RELEASE_OR_BETA
 endif
 ifneq '$(or $(MOZ_DEV_EDITION),$(NIGHTLY_BUILD))' ''
 	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../devtools/shim/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
+	@$(MAKE) multilocale.json-$* AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 
 chrome-%: AB_CD=$*
 chrome-%: IS_LANGUAGE_REPACK=1
 chrome-%:
 	$(if $(filter en-US,$(AB_CD)),, @$(MAKE) merge-$*)
 	@$(MAKE) -C ../../toolkit/locales chrome-$*
 	@$(MAKE) -C ../../services/sync/locales chrome AB_CD=$*
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -575,16 +575,24 @@ toolbarpaletteitem[place=toolbar] > tool
   min-height: 0;
   max-height: 0;
 }
 
 #customization-palette[whimsypong] > toolbarpaletteitem > toolbarspring {
   margin: 0 -7px;
 }
 
+%ifdef XP_UNIX
+%ifndef XP_MACOSX
+#customization-palette[whimsypong] > toolbarpaletteitem > toolbarspring {
+  font-size: 12px;
+}
+%endif
+%endif
+
 #wp-lives,
 #wp-ball {
   /* Don't need HiDPI versions since the size used will be scaled down to 20x20. */
   background-image: url("chrome://browser/skin/customizableui/whimsy.png");
   background-size: contain;
   width: 20px;
 }
 
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -21,21 +21,18 @@
 #downloadsListBox {
   background: transparent;
   color: inherit;
   -moz-appearance: none;
   margin: 0;
 }
 
 #emptyDownloads {
-  padding: 16px 25px;
+  padding: 16px 16px;
   margin: 0;
-  /* The panel can be wider than this description after the blocked subview is
-     shown, so center the text. */
-  text-align: center;
 }
 
 .downloadsPanelFooter {
   background-color: var(--arrowpanel-dimmed);
   border-top: 1px solid var(--panel-separator-color);
 }
 
 .downloadsPanelFooter toolbarseparator,
--- a/caps/tests/mochitest/test_disallowInheritPrincipal.html
+++ b/caps/tests/mochitest/test_disallowInheritPrincipal.html
@@ -27,19 +27,17 @@ var secMan = SpecialPowers.Cc["@mozilla.
                        .getService(nsIScriptSecurityManager);
 var sysPrincipal = secMan.getSystemPrincipal();
 isnot(sysPrincipal, undefined, "Should have a principal");
 isnot(sysPrincipal, null, "Should have a non-null principal");
 is(secMan.isSystemPrincipal(sysPrincipal), true,
    "Should have system principal here");
 
 
-var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"].
-                getService(SpecialPowers.Ci.nsIIOService);
-var inheritingURI = ioService.newURI("javascript:1+1");
+var inheritingURI = SpecialPowers.Services.io.newURI("javascript:1+1");
 
 // First try a normal call to checkLoadURIWithPrincipal
 try {
   secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI,
                                    nsIScriptSecurityManager.STANDARD);
   ok(true, "checkLoadURI allowed the load");
 } catch (e) {
   ok(false, "checkLoadURI failed unexpectedly: " + e);
--- a/config/config.mk
+++ b/config/config.mk
@@ -200,19 +200,22 @@ include $(MOZILLA_DIR)/config/static-che
 
 LDFLAGS		= $(COMPUTED_LDFLAGS) $(PGO_LDFLAGS) $(MK_LDFLAGS)
 
 COMPILE_CFLAGS	= $(COMPUTED_CFLAGS) $(PGO_CFLAGS) $(_DEPEND_CFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(PGO_CFLAGS) $(_DEPEND_CFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
 COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
 ASFLAGS = $(COMPUTED_ASFLAGS)
+SFLAGS = $(COMPUTED_SFLAGS)
 
 HOST_CFLAGS = $(COMPUTED_HOST_CFLAGS) $(_DEPEND_CFLAGS)
 HOST_CXXFLAGS = $(COMPUTED_HOST_CXXFLAGS) $(_DEPEND_CFLAGS)
+HOST_C_LDFLAGS = $(COMPUTED_HOST_C_LDFLAGS)
+HOST_CXX_LDFLAGS = $(COMPUTED_HOST_CXX_LDFLAGS)
 
 # We only add color flags if neither the flag to disable color
 # (e.g. "-fno-color-diagnostics" nor a flag to control color
 # (e.g. "-fcolor-diagnostics=never") is present.
 define colorize_flags
 ifeq (,$(filter $(COLOR_CFLAGS:-f%=-fno-%),$$(1))$(findstring $(COLOR_CFLAGS),$$(1)))
 $(1) += $(COLOR_CFLAGS)
 endif
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -180,16 +180,18 @@ MOZ_PROGRAM_LDFLAGS += -Wl,-rpath -Wl,@e
 endif
 endif
 
 ifeq ($(HOST_OS_ARCH),WINNT)
 HOST_PDBFILE=$(basename $(@F)).pdb
 HOST_PDB_FLAG ?= -Fd$(HOST_PDBFILE)
 HOST_CFLAGS += $(HOST_PDB_FLAG)
 HOST_CXXFLAGS += $(HOST_PDB_FLAG)
+HOST_C_LDFLAGS += $(HOST_PDB_FLAG)
+HOST_CXX_LDFLAGS += $(HOST_PDB_FLAG)
 endif
 
 # Don't build SIMPLE_PROGRAMS during the MOZ_PROFILE_GENERATE pass, and do not
 # attempt to install them
 ifdef MOZ_PROFILE_GENERATE
 $(foreach category,$(INSTALL_TARGETS),\
   $(eval $(category)_FILES := $(foreach file,$($(category)_FILES),$(if $(filter $(SIMPLE_PROGRAMS),$(notdir $(file))),,$(file)))))
 SIMPLE_PROGRAMS :=
@@ -601,19 +603,19 @@ ifdef MSMANIFEST_TOOL
 		fi; \
 	elif test -f '$(srcdir)/$@.manifest'; then \
 		echo 'Embedding manifest from $(srcdir)/$@.manifest'; \
 		$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$@.manifest' -OUTPUTRESOURCE:$@\;1; \
 	fi
 endif	# MSVC with manifest tool
 else
 ifeq ($(HOST_CPP_PROG_LINK),1)
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) -o $@ $(HOST_CXXFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) -o $@ $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) -o $@ $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) -o $@ $(HOST_C_LDFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif # HOST_CPP_PROG_LINK
 endif
 ifndef CROSS_COMPILE
 	$(call CHECK_STDCXX,$@)
 endif
 
 #
 # This is an attempt to support generation of multiple binaries
@@ -646,19 +648,19 @@ ifdef MOZ_POST_PROGRAM_COMMAND
 endif
 
 $(HOST_SIMPLE_PROGRAMS): host_%$(HOST_BIN_SUFFIX): host_%.$(OBJ_SUFFIX) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (WINNT_,$(HOST_OS_ARCH)_$(GNU_CC))
 	$(EXPAND_LIBS_EXEC) -- $(LINK) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 ifneq (,$(HOST_CPPSRCS)$(USE_HOST_CXX))
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXXFLAGS) $(INCLUDES) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(EXPAND_LIBS_EXEC) -- $(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXX_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
-	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_CFLAGS) $(INCLUDES) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_C_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 endif
 ifndef CROSS_COMPILE
 	$(call CHECK_STDCXX,$@)
 endif
 
 $(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
@@ -747,30 +749,30 @@ crate_src_libdep = $(call mk_global_crat
 $(foreach f,$(RSSRCS),$(eval $(call src_libdep,$(f))))
 $(foreach f,$(RS_STATICLIB_CRATE_SRC),$(eval $(call crate_src_libdep,$(f))))
 
 $(OBJS) $(HOST_OBJS) $(PROGOBJS) $(HOST_PROGOBJS): $(GLOBAL_DEPS)
 
 # Rules for building native targets must come first because of the host_ prefix
 $(HOST_COBJS):
 	$(REPORT_BUILD_VERBOSE)
-	$(ELOG) $(HOST_CC) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CFLAGS) $(INCLUDES) $(NSPR_CFLAGS) $(_VPATH_SRCS)
+	$(ELOG) $(HOST_CC) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CFLAGS) $(NSPR_CFLAGS) $(_VPATH_SRCS)
 
 $(HOST_CPPOBJS):
 	$(REPORT_BUILD_VERBOSE)
 	$(call BUILDSTATUS,OBJECT_FILE $@)
-	$(ELOG) $(HOST_CXX) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CXXFLAGS) $(INCLUDES) $(NSPR_CFLAGS) $(_VPATH_SRCS)
+	$(ELOG) $(HOST_CXX) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CXXFLAGS) $(NSPR_CFLAGS) $(_VPATH_SRCS)
 
 $(HOST_CMOBJS):
 	$(REPORT_BUILD_VERBOSE)
-	$(ELOG) $(HOST_CC) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CFLAGS) $(HOST_CMFLAGS) $(INCLUDES) $(NSPR_CFLAGS) $(_VPATH_SRCS)
+	$(ELOG) $(HOST_CC) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CFLAGS) $(HOST_CMFLAGS) $(NSPR_CFLAGS) $(_VPATH_SRCS)
 
 $(HOST_CMMOBJS):
 	$(REPORT_BUILD_VERBOSE)
-	$(ELOG) $(HOST_CXX) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CXXFLAGS) $(HOST_CMMFLAGS) $(INCLUDES) $(NSPR_CFLAGS) $(_VPATH_SRCS)
+	$(ELOG) $(HOST_CXX) $(HOST_OUTOPTION)$@ -c $(HOST_CPPFLAGS) $(HOST_CXXFLAGS) $(HOST_CMMFLAGS) $(NSPR_CFLAGS) $(_VPATH_SRCS)
 
 $(COBJS):
 	$(REPORT_BUILD_VERBOSE)
 	$(ELOG) $(CC) $(OUTOPTION)$@ -c $(COMPILE_CFLAGS) $($(notdir $<)_FLAGS) $(_VPATH_SRCS)
 
 # DEFINES and ACDEFINES are needed here to enable conditional compilation of Q_OBJECTs:
 # 'moc' only knows about #defines it gets on the command line (-D...), not in
 # included headers like mozilla-config.h
@@ -1017,17 +1019,17 @@ force-cargo-host-program-check:
 	$(call CARGO_CHECK) $(addprefix --bin ,$(HOST_RUST_CARGO_PROGRAMS)) $(cargo_host_flag)
 else
 force-cargo-host-program-check:
 	@true
 endif # HOST_RUST_PROGRAMS
 
 $(SOBJS):
 	$(REPORT_BUILD)
-	$(AS) -o $@ $(DEFINES) $(ASFLAGS) $($(notdir $<)_FLAGS) $(LOCAL_INCLUDES) -c $<
+	$(AS) -o $@ $(SFLAGS) $($(notdir $<)_FLAGS) -c $<
 
 $(CPPOBJS):
 	$(REPORT_BUILD_VERBOSE)
 	$(call BUILDSTATUS,OBJECT_FILE $@)
 	$(ELOG) $(CCC) $(OUTOPTION)$@ -c $(COMPILE_CXXFLAGS) $($(notdir $<)_FLAGS) $(_VPATH_SRCS)
 
 $(CMMOBJS):
 	$(REPORT_BUILD_VERBOSE)
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
@@ -1,18 +1,14 @@
 // Return a promise with a reference to jsterm, opening the split
 // console if necessary.  This cleans up the split console pref so
 // it won't pollute other tests.
 function getSplitConsole(dbg) {
   const { toolbox, win } = dbg;
 
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-  });
-
   if (!win) {
     win = toolbox.win;
   }
 
   if (!toolbox.splitConsole) {
     pressKey(dbg, "Escape");
   }
 
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-toggling-tools.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-toggling-tools.js
@@ -1,18 +1,14 @@
 // Return a promise with a reference to jsterm, opening the split
 // console if necessary.  This cleans up the split console pref so
 // it won't pollute other tests.
 function getSplitConsole(dbg) {
   const { toolbox, win } = dbg;
 
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-  });
-
   if (!win) {
     win = toolbox.win;
   }
 
   if (!toolbox.splitConsole) {
     pressKey(dbg, "Escape");
   }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js
@@ -57,11 +57,10 @@ function* runTests() {
   let mainTargetConsoleEvalResult = yield jsterm.execute("$_");
 
   is(
     mainTargetConsoleEvalResult.querySelector(".console-string").textContent,
     '"privateVarValue"',
     "Got the expected split console log on $_ executed on resumed debugger"
   );
 
-  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
   yield closeDebuggerAndFinish(panel);
 }
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -1217,20 +1217,16 @@ function source(sourceClient) {
   info("Getting source.\n");
   return sourceClient.source();
 }
 
 // Return a promise with a reference to jsterm, opening the split
 // console if necessary.  This cleans up the split console pref so
 // it won't pollute other tests.
 function getSplitConsole(toolbox, win) {
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-  });
-
   if (!win) {
     win = toolbox.win;
   }
 
   if (!toolbox.splitConsole) {
     EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
   }
 
--- a/devtools/client/framework/test/browser_toolbox_split_console.js
+++ b/devtools/client/framework/test/browser_toolbox_split_console.js
@@ -72,14 +72,12 @@ function* testUseKeyWithSplitConsoleWron
   let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
   consoleInput.focus();
   synthesizeKeyShortcut("F4", panelWin);
 
   ok(!commandCalled, "Shortcut key shouldn't trigger the command");
 }
 
 function* cleanup() {
-  // We don't want the open split console to confuse other tests..
-  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
   yield gToolbox.destroy();
   gBrowser.removeCurrentTab();
   gToolbox = panelWin = null;
 }
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -113,16 +113,17 @@ function getFrameScript() {
 
 flags.testing = true;
 registerCleanupFunction(() => {
   flags.testing = false;
   Services.prefs.clearUserPref("devtools.dump.emit");
   Services.prefs.clearUserPref("devtools.toolbox.host");
   Services.prefs.clearUserPref("devtools.toolbox.previousHost");
   Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
 });
 
 registerCleanupFunction(function* cleanup() {
   while (gBrowser.tabs.length > 1) {
     yield closeTabAndToolbox(gBrowser.selectedTab);
   }
 });
 
--- a/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
@@ -2,20 +2,16 @@
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Tests "Use in Console" menu item
 
 const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
 
-registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-});
-
 // Use the old webconsole since the node isn't being rendered as an HTML tag
 // in the new one (Bug 1304794)
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 });
 
 add_task(function* () {
--- a/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js
@@ -7,17 +7,19 @@
 const { Component } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { getFormattedSize } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const { propertiesEqual } = require("../utils/request-utils");
 
 const { div } = dom;
-
+const SIZE_CACHED = L10N.getStr("networkMenu.sizeCached");
+const SIZE_SERVICE_WORKER = L10N.getStr("networkMenu.sizeServiceWorker");
+const SIZE_UNAVAILABLE = L10N.getStr("networkMenu.sizeUnavailable");
 const UPDATED_TRANSFERRED_PROPS = [
   "transferredSize",
   "fromCache",
   "fromServiceWorker",
 ];
 
 class RequestListColumnTransferredSize extends Component {
   static get propTypes() {
@@ -30,23 +32,23 @@ class RequestListColumnTransferredSize e
     return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
   }
 
   render() {
     let { fromCache, fromServiceWorker, status, transferredSize } = this.props.item;
     let text;
 
     if (fromCache || status === "304") {
-      text = L10N.getStr("networkMenu.sizeCached");
+      text = SIZE_CACHED;
     } else if (fromServiceWorker) {
-      text = L10N.getStr("networkMenu.sizeServiceWorker");
+      text = SIZE_SERVICE_WORKER;
     } else if (typeof transferredSize == "number") {
       text = getFormattedSize(transferredSize);
     } else if (transferredSize === null) {
-      text = L10N.getStr("networkMenu.sizeUnavailable");
+      text = SIZE_UNAVAILABLE;
     }
 
     return (
       div({ className: "requests-list-column requests-list-transferred", title: text },
         text
       )
     );
   }
--- a/devtools/client/netmonitor/src/components/RequestListHeader.js
+++ b/devtools/client/netmonitor/src/components/RequestListHeader.js
@@ -102,17 +102,17 @@ class RequestListHeader extends Componen
     let { columns, scale, sort, sortBy, waterfallWidth } = this.props;
 
     return (
       div({ className: "devtools-toolbar requests-list-headers-wrapper" },
         div({
           className: "devtools-toolbar requests-list-headers",
           onContextMenu: this.onContextMenu
         },
-          HEADERS.filter((header) => columns.get(header.name)).map((header) => {
+          HEADERS.filter((header) => columns[header.name]).map((header) => {
             let name = header.name;
             let boxName = header.boxName || name;
             let label = header.noLocalization
               ? name : L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
             let sorted, sortedTitle;
             let active = sort.type == name ? true : undefined;
 
             if (active) {
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -2,17 +2,16 @@
  * 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const I = require("devtools/client/shared/vendor/immutable");
 const { propertiesEqual } = require("../utils/request-utils");
 const { RESPONSE_HEADERS } = require("../constants");
 
 // Components
 const RequestListColumnCause = createFactory(require("./RequestListColumnCause"));
 const RequestListColumnContentSize = createFactory(require("./RequestListColumnContentSize"));
 const RequestListColumnCookies = createFactory(require("./RequestListColumnCookies"));
 const RequestListColumnDomain = createFactory(require("./RequestListColumnDomain"));
@@ -95,17 +94,17 @@ class RequestListItem extends Component 
     if (this.props.isSelected) {
       this.refs.listItem.focus();
     }
   }
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_REQ_ITEM_PROPS, this.props.item, nextProps.item) ||
       !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps) ||
-      !I.is(this.props.columns, nextProps.columns);
+      this.props.columns !== nextProps.columns;
   }
 
   componentDidUpdate(prevProps) {
     if (!prevProps.isSelected && this.props.isSelected) {
       this.refs.listItem.focus();
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
@@ -136,42 +135,42 @@ class RequestListItem extends Component 
       div({
         ref: "listItem",
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
       },
-        columns.get("status") && RequestListColumnStatus({ item }),
-        columns.get("method") && RequestListColumnMethod({ item }),
-        columns.get("file") && RequestListColumnFile({ item }),
-        columns.get("protocol") && RequestListColumnProtocol({ item }),
-        columns.get("scheme") && RequestListColumnScheme({ item }),
-        columns.get("domain") && RequestListColumnDomain({ item,
-                                                           onSecurityIconMouseDown }),
-        columns.get("remoteip") && RequestListColumnRemoteIP({ item }),
-        columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeMouseDown }),
-        columns.get("type") && RequestListColumnType({ item }),
-        columns.get("cookies") && RequestListColumnCookies({ connector, item }),
-        columns.get("setCookies") && RequestListColumnSetCookies({ connector, item }),
-        columns.get("transferred") && RequestListColumnTransferredSize({ item }),
-        columns.get("contentSize") && RequestListColumnContentSize({ item }),
-        columns.get("startTime") &&
+        columns.status && RequestListColumnStatus({ item }),
+        columns.method && RequestListColumnMethod({ item }),
+        columns.file && RequestListColumnFile({ item }),
+        columns.protocol && RequestListColumnProtocol({ item }),
+        columns.scheme && RequestListColumnScheme({ item }),
+        columns.domain && RequestListColumnDomain({ item,
+                                                    onSecurityIconMouseDown }),
+        columns.remoteip && RequestListColumnRemoteIP({ item }),
+        columns.cause && RequestListColumnCause({ item, onCauseBadgeMouseDown }),
+        columns.type && RequestListColumnType({ item }),
+        columns.cookies && RequestListColumnCookies({ connector, item }),
+        columns.setCookies && RequestListColumnSetCookies({ connector, item }),
+        columns.transferred && RequestListColumnTransferredSize({ item }),
+        columns.contentSize && RequestListColumnContentSize({ item }),
+        columns.startTime &&
           RequestListColumnStartTime({ item, firstRequestStartedMillis }),
-        columns.get("endTime") &&
+        columns.endTime &&
           RequestListColumnEndTime({ item, firstRequestStartedMillis }),
-        columns.get("responseTime") &&
+        columns.responseTime &&
           RequestListColumnResponseTime({ item, firstRequestStartedMillis }),
-        columns.get("duration") && RequestListColumnDuration({ item }),
-        columns.get("latency") && RequestListColumnLatency({ item }),
-        ...RESPONSE_HEADERS.filter(header => columns.get(header)).map(
+        columns.duration && RequestListColumnDuration({ item }),
+        columns.latency && RequestListColumnLatency({ item }),
+        ...RESPONSE_HEADERS.filter(header => columns[header]).map(
           header => RequestListColumnResponseHeader({ item, header }),
         ),
-        columns.get("waterfall") &&
+        columns.waterfall &&
           RequestListColumnWaterfall({ item, firstRequestStartedMillis,
                                        onWaterfallMouseDown }),
       )
     );
   }
 }
 
 module.exports = RequestListItem;
--- a/devtools/client/netmonitor/src/middleware/prefs.js
+++ b/devtools/client/netmonitor/src/middleware/prefs.js
@@ -37,19 +37,23 @@ function prefsMiddleware(store) {
           "devtools.netmonitor.persistlog", store.getState().ui.persistentLogsEnabled);
         break;
       case DISABLE_BROWSER_CACHE:
         Services.prefs.setBoolPref(
           "devtools.cache.disabled", store.getState().ui.browserCacheDisabled);
         break;
       case TOGGLE_COLUMN:
       case RESET_COLUMNS:
-        let visibleColumns = [...store.getState().ui.columns]
-          .filter(([column, shown]) => shown)
-          .map(([column, shown]) => column);
+        let visibleColumns = [];
+        let columns = store.getState().ui.columns;
+        for (let column in columns) {
+          if (columns[column]) {
+            visibleColumns.push(column);
+          }
+        }
         Services.prefs.setCharPref(
           "devtools.netmonitor.visibleColumns", JSON.stringify(visibleColumns));
         break;
     }
     return res;
   };
 }
 
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -1,15 +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 strict";
 
-const I = require("devtools/client/shared/vendor/immutable");
 const Services = require("Services");
 const {
   CLEAR_REQUESTS,
   OPEN_NETWORK_DETAILS,
   ENABLE_PERSISTENT_LOGS,
   DISABLE_BROWSER_CACHE,
   OPEN_STATISTICS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
@@ -39,75 +38,102 @@ const cols = {
   contentSize: true,
   startTime: false,
   endTime: false,
   responseTime: false,
   duration: false,
   latency: false,
   waterfall: true,
 };
-const Columns = I.Record(
-  Object.assign(
+function Columns() {
+  return Object.assign(
     cols,
     RESPONSE_HEADERS.reduce((acc, header) => Object.assign(acc, { [header]: false }), {})
-  )
-);
+  );
+}
 
-const UI = I.Record({
-  columns: new Columns(),
-  detailsPanelSelectedTab: PANELS.HEADERS,
-  networkDetailsOpen: false,
-  persistentLogsEnabled: Services.prefs.getBoolPref("devtools.netmonitor.persistlog"),
-  browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
-  statisticsOpen: false,
-  waterfallWidth: null,
-});
+function UI(initialState = {}) {
+  return {
+    columns: Columns(),
+    detailsPanelSelectedTab: PANELS.HEADERS,
+    networkDetailsOpen: false,
+    persistentLogsEnabled: Services.prefs.getBoolPref("devtools.netmonitor.persistlog"),
+    browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
+    statisticsOpen: false,
+    waterfallWidth: null,
+    ...initialState,
+  };
+}
 
 function resetColumns(state) {
-  return state.set("columns", new Columns());
+  return {
+    ...state,
+    columns: Columns()
+  };
 }
 
 function resizeWaterfall(state, action) {
-  return state.set("waterfallWidth", action.width);
+  return {
+    ...state,
+    waterfallWidth: action.width
+  };
 }
 
 function openNetworkDetails(state, action) {
-  return state.set("networkDetailsOpen", action.open);
+  return {
+    ...state,
+    networkDetailsOpen: action.open
+  };
 }
 
 function enablePersistentLogs(state, action) {
-  return state.set("persistentLogsEnabled", action.enabled);
+  return {
+    ...state,
+    persistentLogsEnabled: action.enabled
+  };
 }
 
 function disableBrowserCache(state, action) {
-  return state.set("browserCacheDisabled", action.disabled);
+  return {
+    ...state,
+    browserCacheDisabled: action.disabled
+  };
 }
 
 function openStatistics(state, action) {
-  return state.set("statisticsOpen", action.open);
+  return {
+    ...state,
+    statisticsOpen: action.open
+  };
 }
 
 function setDetailsPanelTab(state, action) {
-  return state.set("detailsPanelSelectedTab", action.id);
+  return {
+    ...state,
+    detailsPanelSelectedTab: action.id
+  };
 }
 
 function toggleColumn(state, action) {
   let { column } = action;
 
-  if (!state.has(column)) {
+  if (!state.columns.hasOwnProperty(column)) {
     return state;
   }
 
-  let newState = state.withMutations(columns => {
-    columns.set(column, !state.get(column));
-  });
-  return newState;
+  return {
+    ...state,
+    columns: {
+      ...state.columns,
+      [column]: !state.columns[column]
+    }
+  };
 }
 
-function ui(state = new UI(), action) {
+function ui(state = UI(), action) {
   switch (action.type) {
     case CLEAR_REQUESTS:
       return openNetworkDetails(state, { open: false });
     case OPEN_NETWORK_DETAILS:
       return openNetworkDetails(state, action);
     case ENABLE_PERSISTENT_LOGS:
       return enablePersistentLogs(state, action);
     case DISABLE_BROWSER_CACHE:
@@ -119,17 +145,17 @@ function ui(state = new UI(), action) {
     case REMOVE_SELECTED_CUSTOM_REQUEST:
     case SEND_CUSTOM_REQUEST:
       return openNetworkDetails(state, { open: false });
     case SELECT_DETAILS_PANEL_TAB:
       return setDetailsPanelTab(state, action);
     case SELECT_REQUEST:
       return openNetworkDetails(state, { open: true });
     case TOGGLE_COLUMN:
-      return state.set("columns", toggleColumn(state.columns, action));
+      return toggleColumn(state, action);
     case WATERFALL_RESIZE:
       return resizeWaterfall(state, action);
     default:
       return state;
   }
 }
 
 module.exports = {
--- a/devtools/client/netmonitor/src/request-list-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-context-menu.js
@@ -6,16 +6,17 @@
 
 const Services = require("Services");
 const { Curl } = require("devtools/client/shared/curl");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const { saveAs } = require("devtools/client/shared/file-saver");
 const { copyString } = require("devtools/shared/platform/clipboard");
 const { showMenu } = require("devtools/client/netmonitor/src/utils/menu");
 const { HarExporter } = require("./har/har-exporter");
+const { openRequestInTab } = require("devtools/client/netmonitor/src/utils/firefox/open-request-in-tab");
 const {
   getSelectedRequest,
   getSortedRequests,
 } = require("./selectors/index");
 const { L10N } = require("./utils/l10n");
 const {
   formDataURI,
   getUrlQuery,
@@ -177,17 +178,17 @@ class RequestListContextMenu {
       visible: copySubmenu.slice(15, 16).some((subMenu) => subMenu.visible),
     });
 
     menu.push({
       id: "request-list-context-newtab",
       label: L10N.getStr("netmonitor.context.newTab"),
       accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
       visible: !!selectedRequest,
-      click: () => this.openRequestInTab(url),
+      click: () => this.openRequestInTab(selectedRequest),
     });
 
     menu.push({
       id: "request-list-context-open-in-debugger",
       label: L10N.getStr("netmonitor.context.openInDebugger"),
       accesskey: L10N.getStr("netmonitor.context.openInDebugger.accesskey"),
       visible: !!(selectedRequest && mimeType && mimeType.includes("javascript")),
       click: () => this.openInDebugger(url),
@@ -212,19 +213,18 @@ class RequestListContextMenu {
     });
 
     showMenu(event, menu);
   }
 
   /**
    * Opens selected item in a new tab.
    */
-  openRequestInTab(url) {
-    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
-    win.openUILinkIn(url, "tab", { relatedToCurrent: true });
+  openRequestInTab(selectedRequest) {
+    openRequestInTab(selectedRequest);
   }
 
   /**
    * Opens selected item in the debugger
    */
   openInDebugger(url) {
     let toolbox = gDevTools.getToolbox(this.props.connector.getTabTarget());
     toolbox.viewSourceInDebugger(url, 0);
--- a/devtools/client/netmonitor/src/request-list-header-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-header-context-menu.js
@@ -28,28 +28,35 @@ class RequestListHeaderContextMenu {
 
   get columns() {
     // FIXME: Bug 1362059 - Implement RequestListHeaderContextMenu React component
     // Remove window.store
     return window.store.getState().ui.columns;
   }
 
   get visibleColumns() {
-    return [...this.columns].filter(([_, shown]) => shown);
+    let visible = [];
+    for (let column in this.columns) {
+      if (this.columns[column]) {
+        visible.push(column);
+      }
+    }
+    return visible;
   }
 
   /**
    * Handle the context menu opening.
    */
   open(event = {}) {
     let menu = [];
     let subMenu = { timings: [], responseHeaders: [] };
     let onlyOneColumn = this.visibleColumns.length === 1;
 
-    for (let [column, shown] of this.columns) {
+    for (let column in this.columns) {
+      let shown = this.columns[column];
       let label = nonLocalizedHeaders.includes(column)
           ? stringMap[column] || column
           : L10N.getStr(`netmonitor.toolbar.${stringMap[column] || column}`);
       let entry = {
         id: `request-list-header-${column}-toggle`,
         label,
         type: "checkbox",
         checked: shown,
--- a/devtools/client/netmonitor/src/utils/create-store.js
+++ b/devtools/client/netmonitor/src/utils/create-store.js
@@ -28,17 +28,17 @@ function configureStore(connector) {
   // Prepare initial state.
   const initialState = {
     filters: new Filters({
       requestFilterTypes: getFilterState()
     }),
     requests: new Requests(),
     sort: new Sort(),
     timingMarkers: new TimingMarkers(),
-    ui: new UI({
+    ui: UI({
       columns: getColumnState()
     }),
   };
 
   // Prepare middleware.
   let middleware = applyMiddleware(
     thunk,
     prefs,
@@ -50,26 +50,25 @@ function configureStore(connector) {
 }
 
 // Helpers
 
 /**
  * Get column state from preferences.
  */
 function getColumnState() {
-  let columns = new Columns();
+  let columns = Columns();
   let visibleColumns = getPref("devtools.netmonitor.visibleColumns");
 
-  for (let [col] of columns) {
-    columns = columns.withMutations((state) => {
-      state.set(col, visibleColumns.includes(col));
-    });
+  const state = {};
+  for (let col in columns) {
+    state[col] = visibleColumns.includes(col);
   }
 
-  return columns;
+  return state;
 }
 
 /**
  * Get filter state from preferences.
  */
 function getFilterState() {
   let activeFilters = {};
   let filters = getPref("devtools.netmonitor.filters");
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/utils/firefox/moz.build
@@ -0,0 +1,8 @@
+# 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/.
+
+DevToolsModules(
+    'open-request-in-tab.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint-disable mozilla/reject-some-requires */
+
+"use strict";
+
+let { Cc, Ci } = require("chrome");
+const Services = require("Services");
+const { gDevTools } = require("devtools/client/framework/devtools");
+
+/**
+ * Opens given request in a new tab.
+ */
+function openRequestInTab(request) {
+  let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+  let rawData = request.requestPostData ? request.requestPostData.postData : null;
+  let postData;
+
+  if (rawData && rawData.text) {
+    let stringStream = getInputStreamFromString(rawData.text);
+    postData = Cc["@mozilla.org/network/mime-input-stream;1"]
+      .createInstance(Ci.nsIMIMEInputStream);
+    postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
+    postData.setData(stringStream);
+  }
+
+  win.gBrowser.selectedTab = win.gBrowser.addTab(request.url, null, null, postData);
+}
+
+function getInputStreamFromString(data) {
+  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+    .createInstance(Ci.nsIStringInputStream);
+  stringStream.data = data;
+  return stringStream;
+}
+
+module.exports = {
+  openRequestInTab,
+};
--- a/devtools/client/netmonitor/src/utils/moz.build
+++ b/devtools/client/netmonitor/src/utils/moz.build
@@ -1,19 +1,24 @@
 # 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/.
 
+DIRS += [
+    'firefox',
+]
+
 DevToolsModules(
     'create-store.js',
     'filter-autocomplete-provider.js',
     'filter-predicates.js',
     'filter-text-utils.js',
     'format-utils.js',
     'l10n.js',
     'mdn-utils.js',
     'menu.js',
+    'open-request-in-tab.js',
     'prefs.js',
     'request-utils.js',
     'sort-predicates.js',
     'sort-utils.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/utils/open-request-in-tab.js
@@ -0,0 +1,51 @@
+/* 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 Services = require("Services");
+const { gDevTools } = require("devtools/client/framework/devtools");
+
+/**
+ * Opens given request in a new tab.
+ */
+function openRequestInTab(request) {
+  let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+  if (request.method.toLowerCase() !== "get") {
+    win.openUILinkIn(this.selectedRequest.url, "tab", {
+      relatedToCurrent: true
+    });
+  } else {
+    openRequestInTabHelper({
+      url: request.url,
+      method: request.method,
+      data: request.requestPostData ? request.requestPostData.postData : null,
+    });
+  }
+}
+
+function openRequestInTabHelper({url, method, data}) {
+  let form = document.createElement("form");
+  form.target = "_blank";
+  form.action = url;
+  form.method = method;
+
+  if (data) {
+    for (let key in data) {
+      let input = document.createElement("input");
+      input.name = key;
+      input.value = data[key];
+      form.appendChild(input);
+    }
+  }
+
+  form.hidden = true;
+  document.body.appendChild(form);
+  form.submit();
+  form.remove();
+}
+
+module.exports = {
+  openRequestInTab,
+};
--- a/devtools/client/netmonitor/test/browser_net_columns_last_column.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_last_column.js
@@ -8,19 +8,27 @@
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, store, parent } = monitor.panelWin;
 
-  for (let [column, shown] of store.getState().ui.columns) {
-    let visibleColumns = [...store.getState().ui.columns]
-      .filter(([_, visible]) => visible);
+  let initialColumns = store.getState().ui.columns;
+  for (let column in initialColumns) {
+    let shown = initialColumns[column];
+
+    let columns = store.getState().ui.columns;
+    let visibleColumns = [];
+    for (let c in columns) {
+      if (columns[c]) {
+        visibleColumns.push(c);
+      }
+    }
 
     if (visibleColumns.length === 1) {
       if (!shown) {
         continue;
       }
       yield testLastMenuItem(column);
       break;
     }
--- a/devtools/client/netmonitor/test/browser_net_columns_pref.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_pref.js
@@ -19,25 +19,33 @@ add_task(function* () {
   ok(document.querySelector("#requests-list-status-button"),
      "Status column should be shown");
   ok(document.querySelector("#requests-list-contentSize-button"),
      "Content size column should be shown");
 
   yield hideColumn("status");
   yield hideColumn("contentSize");
 
-  ok(!Services.prefs.getCharPref("devtools.netmonitor.visibleColumns").includes("status"),
-    "Pref should be synced for status");
-  ok(!Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
-    .includes("contentSize"), "Pref should be synced for contentSize");
+  let visibleColumns = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
+  );
+
+  ok(!visibleColumns.includes("status"),
+     "Pref should be synced for status");
+  ok(!visibleColumns.includes("contentSize"),
+    "Pref should be synced for contentSize");
 
   yield showColumn("status");
 
-  ok(Services.prefs.getCharPref("devtools.netmonitor.visibleColumns").includes("status"),
-  "Pref should be synced for status");
+  visibleColumns = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
+  );
+
+  ok(visibleColumns.includes("status"),
+    "Pref should be synced for status");
 
   function* hideColumn(column) {
     info(`Clicking context-menu item for ${column}`);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelector("#requests-list-status-button") ||
       document.querySelector("#requests-list-waterfall-button"));
 
     let onHeaderRemoved = waitForDOM(document, `#requests-list-${column}-button`, 0);
--- a/devtools/client/netmonitor/test/browser_net_columns_reset.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_reset.js
@@ -19,17 +19,17 @@ add_task(function* () {
   hideColumn("status");
   hideColumn("waterfall");
 
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelector("#requests-list-contentSize-button"));
 
   parent.document.querySelector("#request-list-header-reset-columns").click();
 
-  is(JSON.stringify(prefBefore), JSON.stringify(Prefs.visibleColumns),
+  ok(JSON.stringify(prefBefore) === JSON.stringify(Prefs.visibleColumns),
      "Reset columns item should reset columns pref");
 
   function* hideColumn(column) {
     info(`Clicking context-menu item for ${column}`);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelector("#requests-list-contentSize-button"));
 
     let onHeaderRemoved = waitForDOM(document, `#requests-list-${column}-button`, 0);
--- a/devtools/client/netmonitor/test/browser_net_columns_showhide.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_showhide.js
@@ -8,28 +8,30 @@
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, store, parent } = monitor.panelWin;
 
-  for (let [column, shown] of store.getState().ui.columns) {
-    if (shown) {
+  let columns = store.getState().ui.columns;
+  for (let column in columns) {
+    if (columns[column]) {
       yield testVisibleColumnContextMenuItem(column, document, parent);
       yield testHiddenColumnContextMenuItem(column, document, parent);
     } else {
       yield testHiddenColumnContextMenuItem(column, document, parent);
       yield testVisibleColumnContextMenuItem(column, document, parent);
     }
   }
 
-  for (let [column, shown] of store.getState().ui.columns) {
-    if (shown) {
+  columns = store.getState().ui.columns;
+  for (let column in columns) {
+    if (columns[column]) {
       yield testVisibleColumnContextMenuItem(column, document, parent);
       // Right click on the white-space for the context menu to appear
       // and toggle column visibility
       yield testWhiteSpaceContextMenuItem(column, document, parent);
     }
   }
 });
 
--- a/devtools/client/netmonitor/webpack.config.js
+++ b/devtools/client/netmonitor/webpack.config.js
@@ -81,16 +81,17 @@ let webpackConfig = {
       "devtools/client/shared/vendor/reselect": "reselect",
       "devtools/client/shared/vendor/jszip": "jszip",
 
       "devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
 
       "devtools/shared/old-event-emitter": "devtools-modules/src/utils/event-emitter",
       "devtools/shared/fronts/timeline": path.join(__dirname, "../../client/shared/webpack/shims/fronts-timeline-shim"),
       "devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
+      "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab": path.join(__dirname, "src/utils/open-request-in-tab"),
 
       // Locales need to be explicitly mapped to the en-US subfolder
       "devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
       "devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
       "devtools/shim/locales": path.join(__dirname, "../../shared/locales/en-US"),
       "toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
 
       // Unless a path explicitly needs to be rewritten or shimmed, all devtools paths can
--- a/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js
+++ b/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js
@@ -13,20 +13,16 @@ const TEST_URI = "data:text/html;charset
                  "} " +
                  "@media screen and (max-width: 200px) {" +
                  "  div { " +
                  "    width: 100px;" +
                  "  }" +
                  "};" +
                  "</style><div></div></html>";
 
-registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-});
-
 addRDMTask(TEST_URI, function* ({ ui, manager }) {
   info("Open the responsive design mode and set its size to 500x500 to start");
   yield setViewportSize(ui, manager, 500, 500);
 
   info("Open the inspector, rule-view and select the test node");
   let { inspector, view } = yield openRuleView();
   yield selectNode("div", inspector);
 
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -8,17 +8,16 @@
 /* import-globals-from ../../framework/test/shared-head.js */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
-const SPLIT_CONSOLE_PREF = "devtools.toolbox.splitconsoleEnabled";
 const STORAGE_PREF = "devtools.storage.enabled";
 const DOM_CACHE = "dom.caches.enabled";
 const DUMPEMIT_PREF = "devtools.dump.emit";
 const DEBUGGERLOG_PREF = "devtools.debugger.log";
 // Allows Cache API to be working on usage `http` test page
 const CACHES_ON_HTTP_PREF = "dom.caches.testing.enabled";
 const PATH = "browser/devtools/client/storage/test/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
@@ -39,17 +38,16 @@ var gToolbox, gPanelWindow, gWindow, gUI
 Services.prefs.setBoolPref(STORAGE_PREF, true);
 Services.prefs.setBoolPref(CACHES_ON_HTTP_PREF, true);
 registerCleanupFunction(() => {
   gToolbox = gPanelWindow = gWindow = gUI = null;
   Services.prefs.clearUserPref(CACHES_ON_HTTP_PREF);
   Services.prefs.clearUserPref(DEBUGGERLOG_PREF);
   Services.prefs.clearUserPref(DOM_CACHE);
   Services.prefs.clearUserPref(DUMPEMIT_PREF);
-  Services.prefs.clearUserPref(SPLIT_CONSOLE_PREF);
   Services.prefs.clearUserPref(STORAGE_PREF);
 });
 
 /**
  * This generator function opens the given url in a new tab, then sets up the
  * page by waiting for all cookies, indexedDB items etc.
  *
  * @param url {String} The url to be opened in the new tab
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -386,17 +386,16 @@ skip-if = true #	Bug 1403454
 skip-if = true #	Bug 1404359
 [browser_webconsole_show_subresource_security_errors.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_webconsole_sourcemap_css.js]
 [browser_webconsole_sourcemap_error.js]
 [browser_webconsole_sourcemap_invalid.js]
 [browser_webconsole_sourcemap_nosource.js]
 [browser_webconsole_split.js]
-skip-if = true # Bug 1408949
 [browser_webconsole_split_escape_key.js]
 skip-if = true #	Bug 1405647
 [browser_webconsole_split_focus.js]
 skip-if = true #	Bug 1405648
 [browser_webconsole_split_persist.js]
 skip-if = true #	Bug 1405649
 [browser_webconsole_stacktrace_location_debugger_link.js]
 [browser_webconsole_stacktrace_location_scratchpad_link.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split.js
@@ -1,255 +1,209 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for splitting";
+const {Toolbox} = require("devtools/client/framework/toolbox");
 
-function test() {
-  waitForExplicitFinish();
-  // Test is slow on Linux EC2 instances - Bug 962931
-  requestLongerTimeout(2);
+// Test is slow on Linux EC2 instances - Bug 962931
+requestLongerTimeout(2);
 
-  let {Toolbox} = require("devtools/client/framework/toolbox");
+add_task(async function () {
   let toolbox;
 
-  loadTab(TEST_URI).then(testConsoleLoadOnDifferentPanel);
+  await addTab(TEST_URI);
+  await testConsoleLoadOnDifferentPanel();
+  await testKeyboardShortcuts();
+  await checkAllTools();
 
-  function testConsoleLoadOnDifferentPanel() {
+  info("Testing host types");
+  checkHostType(Toolbox.HostType.BOTTOM);
+  checkToolboxUI();
+  await toolbox.switchHost(Toolbox.HostType.SIDE);
+  checkHostType(Toolbox.HostType.SIDE);
+  checkToolboxUI();
+  await toolbox.switchHost(Toolbox.HostType.WINDOW);
+  checkHostType(Toolbox.HostType.WINDOW);
+  checkToolboxUI();
+  await toolbox.switchHost(Toolbox.HostType.BOTTOM);
+
+  async function testConsoleLoadOnDifferentPanel() {
     info("About to check console loads even when non-webconsole panel is open");
 
-    openPanel("inspector").then(() => {
-      toolbox.on("webconsole-ready", () => {
-        ok(true, "Webconsole has been triggered as loaded while another tool " +
-                 "is active");
-        testKeyboardShortcuts();
-      });
-
-      // Opens split console.
-      toolbox.toggleSplitConsole();
-    });
+    await openPanel("inspector");
+    let webconsoleReady = toolbox.once("webconsole-ready");
+    toolbox.toggleSplitConsole();
+    await webconsoleReady;
+    ok(true, "Webconsole has been triggered as loaded while another tool is active");
   }
 
-  function testKeyboardShortcuts() {
+  async function testKeyboardShortcuts() {
     info("About to check that panel responds to ESCAPE keyboard shortcut");
 
-    toolbox.once("split-console", () => {
-      ok(true, "Split console has been triggered via ESCAPE keypress");
-      checkAllTools();
-    });
-
-    // Closes split console.
+    let splitConsoleReady = toolbox.once("split-console");
     EventUtils.sendKey("ESCAPE", toolbox.win);
+    await splitConsoleReady;
+    ok(true, "Split console has been triggered via ESCAPE keypress");
   }
 
-  function checkAllTools() {
+  async function checkAllTools() {
     info("About to check split console with each panel individually.");
+    await openAndCheckPanel("jsdebugger");
+    await openAndCheckPanel("inspector");
+    await openAndCheckPanel("styleeditor");
+    await openAndCheckPanel("performance");
+    await openAndCheckPanel("netmonitor");
 
-    Task.spawn(function* () {
-      yield openAndCheckPanel("jsdebugger");
-      yield openAndCheckPanel("inspector");
-      yield openAndCheckPanel("styleeditor");
-      yield openAndCheckPanel("performance");
-      yield openAndCheckPanel("netmonitor");
-
-      yield checkWebconsolePanelOpened();
-      testBottomHost();
-    });
+    await checkWebconsolePanelOpened();
   }
 
   function getCurrentUIState() {
-    let win = toolbox.win;
     let deck = toolbox.doc.querySelector("#toolbox-deck");
     let webconsolePanel = toolbox.webconsolePanel;
     let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
 
-    let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode)
-      .getPropertyValue("height"));
-    let deckHeight = parseFloat(win.getComputedStyle(deck)
-      .getPropertyValue("height"));
-    let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel)
-      .getPropertyValue("height"));
+    let containerHeight = deck.parentNode.getBoundingClientRect().height;
+    let deckHeight = deck.getBoundingClientRect().height;
+    let webconsoleHeight = webconsolePanel.getBoundingClientRect().height;
     let splitterVisibility = !splitter.getAttribute("hidden");
     let openedConsolePanel = toolbox.currentToolId === "webconsole";
     let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
 
     return {
       deckHeight: deckHeight,
       containerHeight: containerHeight,
       webconsoleHeight: webconsoleHeight,
       splitterVisibility: splitterVisibility,
       openedConsolePanel: openedConsolePanel,
       buttonSelected: cmdButton.classList.contains("checked")
     };
   }
 
-  const checkWebconsolePanelOpened = Task.async(function* () {
+  async function checkWebconsolePanelOpened() {
     info("About to check special cases when webconsole panel is open.");
 
     // Start with console split, so we can test for transition to main panel.
-    yield toolbox.toggleSplitConsole();
+    await toolbox.toggleSplitConsole();
 
     let currentUIState = getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
     ok(currentUIState.buttonSelected, "The command button is selected");
 
-    yield openPanel("webconsole");
+    await openPanel("webconsole");
     currentUIState = getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
     ok(currentUIState.buttonSelected,
        "The command button is still selected.");
 
     // Make sure splitting console does nothing while webconsole is opened
-    yield toolbox.toggleSplitConsole();
+    await toolbox.toggleSplitConsole();
 
     currentUIState = getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
     ok(currentUIState.buttonSelected,
        "The command button is still selected.");
 
     // Make sure that split state is saved after opening another panel
-    yield openPanel("inspector");
+    await openPanel("inspector");
     currentUIState = getCurrentUIState();
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
     ok(currentUIState.buttonSelected,
        "The command button is still selected.");
 
-    yield toolbox.toggleSplitConsole();
-  });
+    await toolbox.toggleSplitConsole();
+  }
 
-  const checkToolboxUI = Task.async(function* () {
+  async function checkToolboxUI() {
     let currentUIState = getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden by default");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 by default");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed by default");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
     ok(!currentUIState.buttonSelected, "The command button is not selected.");
 
-    yield toolbox.toggleSplitConsole();
+    await toolbox.toggleSplitConsole();
 
     currentUIState = getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     is(Math.round(currentUIState.deckHeight + currentUIState.webconsoleHeight),
        currentUIState.containerHeight,
        "Everything adds up to container height");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
     ok(currentUIState.buttonSelected, "The command button is selected.");
 
-    yield toolbox.toggleSplitConsole();
+    await toolbox.toggleSplitConsole();
 
     currentUIState = getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 after toggling");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed after toggling");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
     ok(!currentUIState.buttonSelected, "The command button is not selected.");
-  });
-
-  function openPanel(toolId) {
-    let deferred = defer();
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.showToolbox(target, toolId).then(function (box) {
-      toolbox = box;
-      deferred.resolve();
-    }).catch(console.error);
-    return deferred.promise;
-  }
-
-  function openAndCheckPanel(toolId) {
-    return openPanel(toolId).then(() => {
-      info("Checking toolbox for " + toolId);
-      return checkToolboxUI(toolbox.getCurrentPanel());
-    });
   }
 
-  function testBottomHost() {
-    checkHostType(Toolbox.HostType.BOTTOM);
-
-    checkToolboxUI();
-
-    toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost);
+  async function openPanel(toolId) {
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    toolbox = await gDevTools.showToolbox(target, toolId);
   }
 
-  function testSidebarHost() {
-    checkHostType(Toolbox.HostType.SIDE);
-
-    checkToolboxUI();
-
-    toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost);
-  }
-
-  function testWindowHost() {
-    checkHostType(Toolbox.HostType.WINDOW);
-
-    checkToolboxUI();
-
-    toolbox.switchHost(Toolbox.HostType.BOTTOM).then(testDestroy);
+  async function openAndCheckPanel(toolId) {
+    await openPanel(toolId);
+    await checkToolboxUI(toolbox.getCurrentPanel());
   }
 
   function checkHostType(hostType) {
     is(toolbox.hostType, hostType, "host type is " + hostType);
 
     let pref = Services.prefs.getCharPref("devtools.toolbox.host");
     is(pref, hostType, "host pref is " + hostType);
   }
-
-  function testDestroy() {
-    toolbox.destroy().then(function () {
-      let target = TargetFactory.forTab(gBrowser.selectedTab);
-      gDevTools.showToolbox(target).then(finish);
-    });
-  }
-
-  function finish() {
-    toolbox = null;
-    finishTest();
-  }
-}
+});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_focus.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_focus.js
@@ -54,13 +54,11 @@
     while (activeElement && activeElement.contentDocument) {
       activeElement = activeElement.contentDocument.activeElement;
     }
     return activeElement;
   }
 
   function finish() {
     toolbox = TEST_URI = null;
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
     finishTest();
   }
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_persist.js
@@ -107,13 +107,11 @@
     let contentWindow = toolbox.win;
     contentWindow.focus();
     EventUtils.sendKey("ESCAPE", contentWindow);
     return onceSplitConsole;
   }
 
   function finish() {
     toolbox = TEST_URI = null;
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
     finishTest();
   }
 }
--- a/devtools/client/webconsole/test/browser_webconsole_split_focus.js
+++ b/devtools/client/webconsole/test/browser_webconsole_split_focus.js
@@ -54,13 +54,11 @@
     while (activeElement && activeElement.contentDocument) {
       activeElement = activeElement.contentDocument.activeElement;
     }
     return activeElement;
   }
 
   function finish() {
     toolbox = TEST_URI = null;
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
     finishTest();
   }
 }
--- a/devtools/client/webconsole/test/browser_webconsole_split_persist.js
+++ b/devtools/client/webconsole/test/browser_webconsole_split_persist.js
@@ -107,13 +107,11 @@
     let contentWindow = toolbox.win;
     contentWindow.focus();
     EventUtils.sendKey("ESCAPE", contentWindow);
     return onceSplitConsole;
   }
 
   function finish() {
     toolbox = TEST_URI = null;
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
-    Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
     finishTest();
   }
 }
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/KeyframeUtils.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Move.h"
 #include "mozilla/RangedArray.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoBindingTypes.h"
+#include "mozilla/ServoCSSParser.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimingParams.h"
 #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h" // For PropertyValuesPair etc.
 #include "jsapi.h" // For ForOfIterator etc.
 #include "nsClassHashtable.h"
@@ -555,36 +556,16 @@ KeyframeUtils::IsAnimatableProperty(nsCS
     if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) {
       return true;
     }
   }
 
   return false;
 }
 
-/* static */ already_AddRefed<RawServoDeclarationBlock>
-KeyframeUtils::ParseProperty(nsCSSPropertyID aProperty,
-                             const nsAString& aValue,
-                             nsIDocument* aDocument)
-{
-  MOZ_ASSERT(aDocument);
-
-  NS_ConvertUTF16toUTF8 value(aValue);
-  // FIXME this is using the wrong base uri (bug 1343919)
-  RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
-                                               aDocument->GetDocumentURI(),
-                                               aDocument->NodePrincipal());
-  return Servo_ParseProperty(aProperty,
-                             &value,
-                             data,
-                             ParsingMode::Default,
-                             aDocument->GetCompatibilityMode(),
-                             aDocument->CSSLoader()).Consume();
-}
-
 // ------------------------------------------------------------------
 //
 // Internal helpers
 //
 // ------------------------------------------------------------------
 
 /**
  * Converts a JS object to an IDL sequence<Keyframe>.
@@ -898,18 +879,20 @@ ReportInvalidPropertyValueToConsole(nsCS
 static Maybe<PropertyValuePair>
 MakePropertyValuePair(nsCSSPropertyID aProperty, const nsAString& aStringValue,
                       nsCSSParser& aParser, nsIDocument* aDocument)
 {
   MOZ_ASSERT(aDocument);
   Maybe<PropertyValuePair> result;
 
   if (aDocument->GetStyleBackendType() == StyleBackendType::Servo) {
+    ServoCSSParser::ParsingEnvironment env =
+      ServoCSSParser::GetParsingEnvironment(aDocument);
     RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
-      KeyframeUtils::ParseProperty(aProperty, aStringValue, aDocument);
+      ServoCSSParser::ParseProperty(aProperty, aStringValue, env);
 
     if (servoDeclarationBlock) {
       result.emplace(aProperty, Move(servoDeclarationBlock));
     } else {
       ReportInvalidPropertyValueToConsole(aProperty, aStringValue, aDocument);
     }
     return result;
   }
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -102,29 +102,13 @@ public:
    *
    * @param aProperty The property to check.
    * @param aBackend  The style backend, Servo or Gecko, that should determine
    *                  if the property is animatable or not.
    * @return true if |aProperty| is animatable.
    */
   static bool IsAnimatableProperty(nsCSSPropertyID aProperty,
                                    StyleBackendType aBackend);
-
-  /**
-   * Parse a string representing a CSS property value into a
-   * RawServoDeclarationBlock.
-   *
-   * @param aProperty The property to be parsed.
-   * @param aValue The specified value.
-   * @param aDocument The current document.
-   * @return The parsed value as a RawServoDeclarationBlock. We put the value
-   *   in a declaration block since that is how we represent specified values
-   *   in Servo.
-   */
-  static already_AddRefed<RawServoDeclarationBlock> ParseProperty(
-    nsCSSPropertyID aProperty,
-    const nsAString& aValue,
-    nsIDocument* aDocument);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_KeyframeUtils_h
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/TimingParams.h"
 
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/dom/AnimatableBinding.h"
 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
-#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
 #include "nsCSSParser.h" // For nsCSSParser
 #include "nsIDocument.h"
 #include "nsRuleNode.h"
 
 namespace mozilla {
 
 template <class OptionsType>
 static const dom::AnimationEffectTimingProperties&
@@ -114,21 +114,18 @@ TimingParams::FromOptionsUnion(
 TimingParams::ParseEasing(const nsAString& aEasing,
                           nsIDocument* aDocument,
                           ErrorResult& aRv)
 {
   MOZ_ASSERT(aDocument);
 
   if (aDocument->IsStyledByServo()) {
     nsTimingFunction timingFunction;
-    // FIXME this is using the wrong base uri (bug 1343919)
-    RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
-                                                 aDocument->GetDocumentURI(),
-                                                 aDocument->NodePrincipal());
-    if (!Servo_ParseEasing(&aEasing, data, &timingFunction)) {
+    RefPtr<URLExtraData> url = ServoCSSParser::GetURLExtraData(aDocument);
+    if (!ServoCSSParser::ParseEasing(aEasing, url, timingFunction)) {
       aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>(aEasing);
       return Nothing();
     }
 
     if (timingFunction.mType == nsTimingFunction::Type::Linear) {
       return Nothing();
     }
 
--- a/dom/base/DOMMatrix.cpp
+++ b/dom/base/DOMMatrix.cpp
@@ -7,17 +7,17 @@
 #include "mozilla/dom/DOMMatrix.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMMatrixBinding.h"
 #include "mozilla/dom/DOMPoint.h"
 #include "mozilla/dom/DOMPointBinding.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ToJSValue.h"
-#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoCSSParser.h"
 #include "nsCSSParser.h"
 #include "nsStyleTransformMatrix.h"
 
 #include <math.h>
 
 namespace mozilla {
 namespace dom {
 
@@ -671,20 +671,19 @@ DOMMatrix::SetMatrixValue(const nsAStrin
   // An empty string is a no-op.
   if (aTransformList.IsEmpty()) {
     return this;
   }
 
   gfx::Matrix4x4 transform;
   bool contains3dTransform = false;
   if (mIsServo) {
-    bool status = Servo_ParseTransformIntoMatrix(&aTransformList,
-                                                 &contains3dTransform,
-                                                 &transform.components);
-    if (!status) {
+    if (!ServoCSSParser::ParseTransformIntoMatrix(aTransformList,
+                                                  contains3dTransform,
+                                                  transform.components)) {
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return nullptr;
     }
   } else {
     nsCSSValue value;
     nsCSSParser parser;
     bool parseSuccess = parser.ParseTransformProperty(aTransformList,
                                                       true,
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2979,41 +2979,16 @@ Element::OnAttrSetButNotChanged(int32_t 
 EventListenerManager*
 Element::GetEventListenerManagerForAttr(nsAtom* aAttrName,
                                         bool* aDefer)
 {
   *aDefer = true;
   return GetOrCreateListenerManager();
 }
 
-BorrowedAttrInfo
-Element::GetAttrInfo(int32_t aNamespaceID, nsAtom* aName) const
-{
-  NS_ASSERTION(nullptr != aName, "must have attribute name");
-  NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
-               "must have a real namespace ID!");
-
-  int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNamespaceID);
-  if (index < 0) {
-    return BorrowedAttrInfo(nullptr, nullptr);
-  }
-
-  return mAttrsAndChildren.AttrInfoAt(index);
-}
-
-BorrowedAttrInfo
-Element::GetAttrInfoAt(uint32_t aIndex) const
-{
-  if (aIndex >= mAttrsAndChildren.AttrCount()) {
-    return BorrowedAttrInfo(nullptr, nullptr);
-  }
-
-  return mAttrsAndChildren.AttrInfoAt(aIndex);
-}
-
 bool
 Element::GetAttr(int32_t aNameSpaceID, nsAtom* aName,
                  nsAString& aResult) const
 {
   DOMString str;
   bool haveAttr = GetAttr(aNameSpaceID, aName, str);
   str.ToString(aResult);
   return haveAttr;
@@ -3161,28 +3136,16 @@ Element::UnsetAttr(int32_t aNameSpaceID,
 
     mozAutoSubtreeModified subtree(OwnerDoc(), this);
     (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
   }
 
   return NS_OK;
 }
 
-const nsAttrName*
-Element::GetAttrNameAt(uint32_t aIndex) const
-{
-  return mAttrsAndChildren.GetSafeAttrNameAt(aIndex);
-}
-
-uint32_t
-Element::GetAttrCount() const
-{
-  return mAttrsAndChildren.AttrCount();
-}
-
 void
 Element::DescribeAttribute(uint32_t index, nsAString& aOutDescription) const
 {
   // name
   mAttrsAndChildren.AttrNameAt(index)->GetQualifiedName(aOutDescription);
 
   // value
   aOutDescription.AppendLiteral("=\"");
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -778,19 +778,36 @@ public:
                           nsAtom* aValue,
                           nsCaseTreatment aCaseSensitive) const;
   virtual int32_t FindAttrValueIn(int32_t aNameSpaceID,
                                   nsAtom* aName,
                                   AttrValuesArray* aValues,
                                   nsCaseTreatment aCaseSensitive) const override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsAtom* aAttribute,
                              bool aNotify) override;
-  virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const override;
-  virtual BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const override;
-  virtual uint32_t GetAttrCount() const override;
+
+  virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const final override
+  {
+    return mAttrsAndChildren.GetSafeAttrNameAt(aIndex);
+  }
+
+  virtual BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const final override
+  {
+    if (aIndex >= mAttrsAndChildren.AttrCount()) {
+      return BorrowedAttrInfo(nullptr, nullptr);
+    }
+
+    return mAttrsAndChildren.AttrInfoAt(aIndex);
+  }
+
+  virtual uint32_t GetAttrCount() const final override
+  {
+    return mAttrsAndChildren.AttrCount();
+  }
+
   virtual bool IsNodeOfType(uint32_t aFlags) const override;
 
   /**
    * Get the class list of this element (this corresponds to the value of the
    * class attribute).  This may be null if there are no classes, but that's not
    * guaranteed (e.g. we could have class="").
    */
   const nsAttrValue* GetClasses() const {
@@ -1317,17 +1334,29 @@ public:
   /**
    * Get the attr info for the given namespace ID and attribute name.  The
    * namespace ID must not be kNameSpaceID_Unknown and the name must not be
    * null.  Note that this can only return info on attributes that actually
    * live on this element (and is only virtual to handle XUL prototypes).  That
    * is, this should only be called from methods that only care about attrs
    * that effectively live in mAttrsAndChildren.
    */
-  virtual BorrowedAttrInfo GetAttrInfo(int32_t aNamespaceID, nsAtom* aName) const;
+  BorrowedAttrInfo GetAttrInfo(int32_t aNamespaceID, nsAtom* aName) const
+  {
+    NS_ASSERTION(aName, "must have attribute name");
+    NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+                 "must have a real namespace ID!");
+
+    int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNamespaceID);
+    if (index < 0) {
+      return BorrowedAttrInfo(nullptr, nullptr);
+    }
+
+    return mAttrsAndChildren.AttrInfoAt(index);
+  }
 
   /**
    * Called when we have been adopted, and the information of the
    * node has been changed.
    *
    * The new document can be reached via OwnerDoc().
    *
    * If you override this method,
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9868,17 +9868,17 @@ nsDocument::ForgetImagePreload(nsIURI* a
     if (req) {
       // Make sure to cancel the request so imagelib knows it's gone.
       req->CancelAndForgetObserver(NS_BINDING_ABORTED);
     }
   }
 }
 
 void
-nsDocument::UpdatePossiblyStaleDocumentState()
+nsIDocument::UpdatePossiblyStaleDocumentState()
 {
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
     if (IsDocumentRightToLeft()) {
       mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
   }
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
@@ -9886,29 +9886,16 @@ nsDocument::UpdatePossiblyStaleDocumentS
     if (shell && shell->GetPresContext() &&
         shell->GetPresContext()->IsTopLevelWindowInactive()) {
       mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
   }
 }
 
-EventStates
-nsDocument::ThreadSafeGetDocumentState() const
-{
-  return mDocumentState;
-}
-
-EventStates
-nsDocument::GetDocumentState()
-{
-  UpdatePossiblyStaleDocumentState();
-  return ThreadSafeGetDocumentState();
-}
-
 namespace {
 
 /**
  * Stub for LoadSheet(), since all we want is to get the sheet into
  * the CSSLoader's style cache
  */
 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
   ~StubCSSLoaderObserver() {}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -838,23 +838,16 @@ public:
                             ReferrerPolicy aReferrerPolicy,
                             const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        RefPtr<mozilla::StyleSheet>* aSheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
-  virtual mozilla::EventStates GetDocumentState() final;
-  // GetDocumentState() mutates the state due to lazy resolution;
-  // and can't be used during parallel traversal. Use this instead,
-  // and ensure GetDocumentState() has been called first.
-  // This will assert if the state is stale.
-  virtual mozilla::EventStates ThreadSafeGetDocumentState() const final;
-
   // Only BlockOnload should call this!
   void AsyncBlockOnload();
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
   virtual void ScrollToRef() override;
   virtual void ResetScrolledToRefAlready() override;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override;
 
@@ -1177,18 +1170,16 @@ protected:
   // non-null when this document is in fullscreen mode.
   nsWeakPtr mFullscreenRoot;
 
   RefPtr<PrincipalFlashClassifier> mPrincipalFlashClassifier;
   mozilla::dom::FlashClassification mFlashClassification;
   // Do not use this value directly. Call the |IsThirdParty()| method, which
   // caches its result here.
   mozilla::Maybe<bool> mIsThirdParty;
-private:
-  void UpdatePossiblyStaleDocumentState();
 
 public:
   RefPtr<mozilla::EventListenerManager> mListenerManager;
   RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
   RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
   RefPtr<mozilla::dom::ScriptLoader> mScriptLoader;
   nsDocHeaderData* mHeaderData;
   /* mIdentifierMap works as follows for IDs:
@@ -1265,19 +1256,16 @@ public:
   uint32_t mUpdateNestLevel;
 
   // The application cache that this document is associated with, if
   // any.  This can change during the lifetime of the document.
   nsCOMPtr<nsIApplicationCache> mApplicationCache;
 
   nsCOMPtr<nsIContent> mFirstBaseNodeWithHref;
 
-  mozilla::EventStates mDocumentState;
-  mozilla::EventStates mGotDocumentState;
-
   RefPtr<nsDOMNavigationTiming> mTiming;
 private:
   friend class nsUnblockOnloadEvent;
   // Recomputes the visibility state but doesn't set the new value.
   mozilla::dom::VisibilityState GetVisibilityState() const;
   void NotifyStyleSheetAdded(mozilla::StyleSheet* aSheet, bool aDocumentSheet);
   void NotifyStyleSheetRemoved(mozilla::StyleSheet* aSheet, bool aDocumentSheet);
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2636,18 +2636,30 @@ public:
   virtual DocumentTheme GetDocumentLWTheme() { return Doc_Theme_None; }
   virtual DocumentTheme ThreadSafeGetDocumentLWTheme() const { return Doc_Theme_None; }
 
   /**
    * Returns the document state.
    * Document state bits have the form NS_DOCUMENT_STATE_* and are declared in
    * nsIDocument.h.
    */
-  virtual mozilla::EventStates GetDocumentState() = 0;
-  virtual mozilla::EventStates ThreadSafeGetDocumentState() const = 0;
+  mozilla::EventStates GetDocumentState()
+  {
+    UpdatePossiblyStaleDocumentState();
+    return ThreadSafeGetDocumentState();
+  }
+
+  // GetDocumentState() mutates the state due to lazy resolution;
+  // and can't be used during parallel traversal. Use this instead,
+  // and ensure GetDocumentState() has been called first.
+  // This will assert if the state is stale.
+  mozilla::EventStates ThreadSafeGetDocumentState() const
+  {
+    return mDocumentState;
+  }
 
   virtual nsISupports* GetCurrentContentSink() = 0;
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
   virtual void ScrollToRef() = 0;
   virtual void ResetScrolledToRefAlready() = 0;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) = 0;
 
@@ -3258,16 +3270,18 @@ protected:
   }
 
   bool GetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mChildDocumentUseCounters[aUseCounter];
   }
 
 private:
+  void UpdatePossiblyStaleDocumentState();
+
   mutable std::bitset<eDeprecatedOperationCount> mDeprecationWarnedAbout;
   mutable std::bitset<eDocumentWarningCount> mDocWarningWarnedAbout;
 
   // Lazy-initialization to have mDocGroup initialized in prior to the
   // SelectorCaches.
   // FIXME(emilio): We can use a single cache when all CSSOM methods are
   // implemented for the Servo backend.
   mozilla::UniquePtr<SelectorCache> mServoSelectorCache;
@@ -3417,16 +3431,19 @@ protected:
 
   // container for per-context fonts (downloadable, SVG, etc.)
   RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
 
   // Last time this document or a one of its sub-documents was focused.  If
   // focus has never occurred then mLastFocusTime.IsNull() will be true.
   mozilla::TimeStamp mLastFocusTime;
 
+  mozilla::EventStates mDocumentState;
+  mozilla::EventStates mGotDocumentState;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
--- a/dom/base/nsNameSpaceManager.h
+++ b/dom/base/nsNameSpaceManager.h
@@ -38,25 +38,22 @@ public:
   NS_DECL_NSIOBSERVER
   virtual nsresult RegisterNameSpace(const nsAString& aURI,
                                      int32_t& aNameSpaceID);
 
   virtual nsresult GetNameSpaceURI(int32_t aNameSpaceID, nsAString& aURI);
 
   // Returns the atom for the namespace URI associated with the given ID. The
   // ID must be within range and not be kNameSpaceID_None (i.e. zero);
+  //
+  // NB: The requirement of mapping from the first entry to the empty atom is
+  // necessary for Servo, though it can be removed if needed adding a branch in
+  // GeckoElement::get_namespace().
   nsAtom* NameSpaceURIAtom(int32_t aNameSpaceID) {
     MOZ_ASSERT(aNameSpaceID > 0);
-    return NameSpaceURIAtomForServo(aNameSpaceID);
-  }
-
-  // NB: This function should only be called by Servo code (and the above
-  // accessor), which uses the empty atom to represent kNameSpaceID_None.
-  nsAtom* NameSpaceURIAtomForServo(int32_t aNameSpaceID) {
-    MOZ_ASSERT(aNameSpaceID >= 0);
     MOZ_ASSERT((int64_t) aNameSpaceID < (int64_t) mURIArray.Length());
     return mURIArray.ElementAt(aNameSpaceID);
   }
 
   int32_t GetNameSpaceID(const nsAString& aURI,
                          bool aInChromeDoc);
   int32_t GetNameSpaceID(nsAtom* aURI,
                          bool aInChromeDoc);
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1158,23 +1158,44 @@ CanvasRenderingContext2D::WrapObject(JSC
 {
   return CanvasRenderingContext2DBinding::Wrap(aCx, this, aGivenProto);
 }
 
 bool
 CanvasRenderingContext2D::ParseColor(const nsAString& aString,
                                      nscolor* aColor)
 {
-  nsIDocument* document = mCanvasElement
-                          ? mCanvasElement->OwnerDoc()
-                          : nullptr;
+  nsIDocument* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
+  css::Loader* loader = document ? document->CSSLoader() : nullptr;
+
+  // FIXME(bug 1420026).
+  if (false) {
+    nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+    ServoStyleSet* set = presShell ? presShell->StyleSet()->AsServo() : nullptr;
+
+    // First, try computing the color without handling currentcolor.
+    bool wasCurrentColor = false;
+    if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor,
+                                      &wasCurrentColor, loader)) {
+      return false;
+    }
+
+    if (wasCurrentColor) {
+      // Otherwise, get the value of the color property, flushing style
+      // if necessary.
+      RefPtr<nsStyleContext> canvasStyle =
+        nsComputedDOMStyle::GetStyleContext(mCanvasElement, nullptr, presShell);
+      *aColor = canvasStyle->StyleColor()->mColor;
+    }
+    return true;
+  }
 
   // Pass the CSS Loader object to the parser, to allow parser error
   // reports to include the outer window ID.
-  nsCSSParser parser(document ? document->CSSLoader() : nullptr);
+  nsCSSParser parser(loader);
   nsCSSValue value;
   if (!parser.ParseColorString(aString, nullptr, 0, value)) {
     return false;
   }
 
   if (value.IsNumericColorUnit()) {
     // if we already have a color we can just use it directly
     *aColor = value.GetColorValue();
@@ -2816,25 +2837,21 @@ CreateDeclarationForServo(nsCSSPropertyI
                           const nsAString& aPropertyValue,
                           nsIDocument* aDocument)
 {
   RefPtr<URLExtraData> data =
     new URLExtraData(aDocument->GetDocBaseURI(),
                      aDocument->GetDocumentURI(),
                      aDocument->NodePrincipal());
 
-  NS_ConvertUTF16toUTF8 value(aPropertyValue);
-
+  ServoCSSParser::ParsingEnvironment env(data,
+                                         aDocument->GetCompatibilityMode(),
+                                         aDocument->CSSLoader());
   RefPtr<RawServoDeclarationBlock> servoDeclarations =
-    Servo_ParseProperty(aProperty,
-                        &value,
-                        data,
-                        ParsingMode::Default,
-                        aDocument->GetCompatibilityMode(),
-                        aDocument->CSSLoader()).Consume();
+    ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env);
 
   if (!servoDeclarations) {
     // We got a syntax error.  The spec says this value must be ignored.
     return nullptr;
   }
 
   // From canvas spec, force to set line-height property to 'normal' font
   // property.
--- a/dom/encoding/FallbackEncoding.cpp
+++ b/dom/encoding/FallbackEncoding.cpp
@@ -13,21 +13,42 @@
 #include "nsIObserverService.h"
 #include "nsUConvPropertySearch.h"
 
 using mozilla::intl::LocaleService;
 
 namespace mozilla {
 namespace dom {
 
-static constexpr nsUConvProp localesFallbacks[] = {
+struct EncodingProp
+{
+  const char* const mKey;
+  NotNull<const Encoding*> mValue;
+};
+
+template <int32_t N>
+static NotNull<const Encoding*>
+SearchEncodingProp(const EncodingProp (&aProperties)[N],
+                   const nsACString& aKey)
+{
+  const nsCString& flat = PromiseFlatCString(aKey);
+  size_t index;
+  if (!BinarySearchIf(aProperties, 0, ArrayLength(aProperties),
+                      [&flat](const EncodingProp& aProperty)
+                      { return flat.Compare(aProperty.mKey); }, &index)) {
+    return WINDOWS_1252_ENCODING;
+  }
+  return aProperties[index].mValue;
+}
+
+static const EncodingProp localesFallbacks[] = {
 #include "localesfallbacks.properties.h"
 };
 
-static constexpr nsUConvProp domainsFallbacks[] = {
+static const EncodingProp domainsFallbacks[] = {
 #include "domainsfallbacks.properties.h"
 };
 
 static constexpr nsUConvProp nonParticipatingDomains[] = {
 #include "nonparticipatingdomains.properties.h"
 };
 
 NS_IMPL_ISUPPORTS(FallbackEncoding, nsIObserver)
@@ -86,25 +107,20 @@ FallbackEncoding::Get()
 
   // Throw away regions and other variants to accommodate weird stuff seen
   // in telemetry--apparently unofficial language packs.
   int32_t index = locale.FindChar('-');
   if (index >= 0) {
     locale.Truncate(index);
   }
 
-  nsAutoCString fallback;
-  if (NS_FAILED(nsUConvPropertySearch::SearchPropertyValue(
-      localesFallbacks, ArrayLength(localesFallbacks), locale, fallback))) {
-    mFallback = WINDOWS_1252_ENCODING;
-  } else {
-    mFallback = Encoding::ForName(fallback);
-  }
+  auto fallback = SearchEncodingProp(localesFallbacks, locale);
+  mFallback = fallback;
 
-  return WrapNotNull(mFallback);
+  return fallback;
 }
 
 NotNull<const Encoding*>
 FallbackEncoding::FromLocale()
 {
   MOZ_ASSERT(FallbackEncoding::sInstance,
              "Using uninitialized fallback cache.");
   return FallbackEncoding::sInstance->Get();
@@ -171,18 +187,13 @@ FallbackEncoding::IsParticipatingTopLeve
       ArrayLength(nonParticipatingDomains),
       aTLD,
       dummy));
 }
 
 NotNull<const Encoding*>
 FallbackEncoding::FromTopLevelDomain(const nsACString& aTLD)
 {
-  nsAutoCString fallback;
-  if (NS_FAILED(nsUConvPropertySearch::SearchPropertyValue(
-      domainsFallbacks, ArrayLength(domainsFallbacks), aTLD, fallback))) {
-    return WINDOWS_1252_ENCODING;
-  }
-  return Encoding::ForName(fallback);
+  return SearchEncodingProp(domainsFallbacks, aTLD);
 }
 
 } // namespace dom
 } // namespace mozilla
copy from intl/locale/props2arrays.py
copy to dom/encoding/encodings2arrays.py
--- a/intl/locale/props2arrays.py
+++ b/dom/encoding/encodings2arrays.py
@@ -16,12 +16,12 @@ def main(header, propFile):
           mappings[parts[0].strip()] = parts[1].strip()
  
   keys = mappings.keys()
   keys.sort()
 
   header.write("// This is a generated file. Please do not edit.\n")
   header.write("// Please edit the corresponding .properties file instead.\n")
 
-  entries = ['{ "%s", "%s", %d }'
-             % (key, mappings[key], len(mappings[key])) for key in keys]
+  entries = ['{ "%s", %s }'
+             % (key, mappings[key].replace('-', '_').upper() + '_ENCODING') for key in keys]
   header.write(',\n'.join(entries) + '\n')
 
--- a/dom/encoding/moz.build
+++ b/dom/encoding/moz.build
@@ -19,29 +19,35 @@ UNIFIED_SOURCES += [
     'TextEncoder.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/intl/locale',
 ]
 
-props2arrays = '/intl/locale/props2arrays.py'
+props2arrays = 'encodings2arrays.py'
 prefixes = (
     'domainsfallbacks',
     'labelsencodings',
     'localesfallbacks',
-    'nonparticipatingdomains',
 )
 
 for prefix in prefixes:
     input_file = prefix + '.properties'
     header = prefix + '.properties.h'
     GENERATED_FILES += [header]
     props = GENERATED_FILES[header]
     props.script = props2arrays
     props.inputs = [input_file]
 
+input_file = 'nonparticipatingdomains.properties'
+header = input_file + '.h'
+GENERATED_FILES += [header]
+props = GENERATED_FILES[header]
+props.script = '../../intl/locale/props2arrays.py'
+props.inputs = [input_file]
+
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
 ]
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/dom/media/tests/mochitest/test_peerConnection_transceivers.html
+++ b/dom/media/tests/mochitest/test_peerConnection_transceivers.html
@@ -976,21 +976,21 @@
 
     hasProps(pc1.getTransceivers(),
       [
         {
           stopped: true,
           mid: "sdparta_0"
         }
       ]);
+    await negotiationNeeded(pc1);
 
     trickle(pc2, pc1);
     await pc2.setLocalDescription(answer);
 
-    await negotiationNeeded(pc1);
     await iceConnected(pc1);
     await iceConnected(pc2);
 
     offer = await pc1.createOffer();
     await pc1.setLocalDescription(offer);
     await pc2.setRemoteDescription(offer);
     answer = await pc2.createAnswer();
     await pc2.setLocalDescription(answer);
--- a/dom/smil/nsSMILCSSValueType.cpp
+++ b/dom/smil/nsSMILCSSValueType.cpp
@@ -13,16 +13,17 @@
 #include "nsSMILParserUtils.h"
 #include "nsSMILValue.h"
 #include "nsCSSProps.h"
 #include "nsCSSValue.h"
 #include "nsColor.h"
 #include "nsPresContext.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StyleAnimationValue.h" // For AnimationValue
+#include "mozilla/ServoCSSParser.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For CompositeOperation
 #include "mozilla/dom/Element.h"
 #include "nsDebug.h"
 #include "nsStyleUtil.h"
 #include "nsIDocument.h"
 
 using namespace mozilla::dom;
@@ -707,29 +708,22 @@ ValueFromStringHelper(nsCSSPropertyID aP
   ServoAnimationValues result;
 
   nsIDocument* doc = aTargetElement->GetUncomposedDoc();
   if (!doc) {
     return result;
   }
 
   // Parse property
-  // FIXME this is using the wrong base uri (bug 1343919)
-  RefPtr<URLExtraData> data = new URLExtraData(doc->GetDocumentURI(),
-                                               doc->GetDocumentURI(),
-                                               doc->NodePrincipal());
-  NS_ConvertUTF16toUTF8 value(aString);
+  ServoCSSParser::ParsingEnvironment env =
+    ServoCSSParser::GetParsingEnvironment(doc);
   RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
-    Servo_ParseProperty(aPropID,
-                        &value,
-                        data,
-                        ParsingMode::AllowUnitlessLength |
-                        ParsingMode::AllowAllNumericValues,
-                        doc->GetCompatibilityMode(),
-                        doc->CSSLoader()).Consume();
+    ServoCSSParser::ParseProperty(aPropID, aString, env,
+                                  ParsingMode::AllowUnitlessLength |
+                                    ParsingMode::AllowAllNumericValues);
   if (!servoDeclarationBlock) {
     return result;
   }
 
   // Compute value
   aPresContext->StyleSet()->AsServo()->GetAnimationValues(servoDeclarationBlock,
                                                           aTargetElement,
                                                           aStyleContext->AsServo(),
--- a/gfx/cairo/libpixman/src/moz.build
+++ b/gfx/cairo/libpixman/src/moz.build
@@ -105,18 +105,18 @@ elif 'arm' in CONFIG['OS_TEST']:
 
 if use_mmx:
     DEFINES['USE_MMX'] = True
     SOURCES += ['pixman-mmx.c']
     SOURCES['pixman-mmx.c'].flags += CONFIG['MMX_FLAGS']
     if CONFIG['GNU_CC']:
         SOURCES['pixman-mmx.c'].flags += [
             '-Winline',
-            '--param inline-unit-growth=10000',
-            '--param large-function-growth=10000',
+            '--param', 'inline-unit-growth=10000',
+            '--param', 'large-function-growth=10000',
         ]
 
 if use_sse2:
     DEFINES['USE_SSE'] = True
     DEFINES['USE_SSE2'] = True
     SOURCES += ['pixman-sse2.c']
     SOURCES['pixman-sse2.c'].flags += CONFIG['SSE_FLAGS'] + CONFIG['SSE2_FLAGS']
     if CONFIG['GNU_CC']:
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -170,9 +170,9 @@ 2. Sometimes autoland tip has changed en
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
 The version of WebRender currently in the tree is:
-e3dd85359580074f4ca4a554d9a3c85779f8de64
+b7714b1d4348c00682b5643ea0e3f0b15adaeda5
--- a/gfx/thebes/gfxImageSurface.cpp
+++ b/gfx/thebes/gfxImageSurface.cpp
@@ -91,19 +91,19 @@ gfxImageSurface::InitWithData(unsigned c
 static void*
 TryAllocAlignedBytes(size_t aSize)
 {
     // Use fallible allocators here
 #if defined(HAVE_POSIX_MEMALIGN)
     void* ptr;
     // Try to align for fast alpha recovery.  This should only help
     // cairo too, can't hurt.
-    return moz_posix_memalign(&ptr,
-                              1 << gfxAlphaRecovery::GoodAlignmentLog2(),
-                              aSize) ?
+    return posix_memalign(&ptr,
+                          1 << gfxAlphaRecovery::GoodAlignmentLog2(),
+                          aSize) ?
              nullptr : ptr;
 #else
     // Oh well, hope that luck is with us in the allocator
     return malloc(aSize);
 #endif
 }
 
 gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, bool aClear)
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -72,16 +72,20 @@ VRDisplayHost::VRDisplayHost(VRDeviceTyp
   mDisplayInfo.mPresentingGroups = 0;
   mDisplayInfo.mGroupMask = kVRGroupContent;
   mDisplayInfo.mFrameId = 0;
   mDisplayInfo.mPresentingGeneration = 0;
 }
 
 VRDisplayHost::~VRDisplayHost()
 {
+  if (mSubmitThread) {
+    mSubmitThread->Shutdown();
+    mSubmitThread = nullptr;
+  }
   MOZ_COUNT_DTOR(VRDisplayHost);
 }
 
 #if defined(XP_WIN)
 bool
 VRDisplayHost::CreateD3DObjects()
 {
   if (!mDevice) {
@@ -248,33 +252,24 @@ VRDisplayHost::NotifyVSync()
   if (bShouldStartFrame) {
     VRManager *vm = VRManager::Get();
     MOZ_ASSERT(vm);
     vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
   }
 }
 
 void
-VRDisplayHost::SubmitFrame(VRLayerParent* aLayer,
-                           const layers::SurfaceDescriptor &aTexture,
-                           uint64_t aFrameId,
-                           const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect)
+VRDisplayHost::SubmitFrameInternal(const layers::SurfaceDescriptor &aTexture,
+                                   uint64_t aFrameId,
+                                   const gfx::Rect& aLeftEyeRect,
+                                   const gfx::Rect& aRightEyeRect)
 {
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayHost");
 
-  if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
-    // Suppress layers hidden by the group mask
-    return;
-  }
-
-  // Ensure that we only accept the first SubmitFrame call per RAF cycle.
-  if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
-    return;
-  }
   mFrameStarted = false;
   switch (aTexture.type()) {
 
 #if defined(XP_WIN)
     case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
       if (!CreateD3DObjects()) {
         return;
       }
@@ -331,21 +326,21 @@ VRDisplayHost::SubmitFrame(VRLayerParent
                                      surf->GetDevicePixelHeight());
       if (!SubmitFrame(surf, texSize, aLeftEyeRect, aRightEyeRect)) {
         return;
       }
       break;
     }
 #elif defined(MOZ_ANDROID_GOOGLE_VR)
     case SurfaceDescriptor::TEGLImageDescriptor: {
-       const EGLImageDescriptor& desc = aTexture.get_EGLImageDescriptor();
-       if (!SubmitFrame(&desc, aLeftEyeRect, aRightEyeRect)) {
-         return;
-       }
-       break;
+      const EGLImageDescriptor& desc = aTexture.get_EGLImageDescriptor();
+      if (!SubmitFrame(&desc, aLeftEyeRect, aRightEyeRect)) {
+        return;
+      }
+      break;
     }
 #endif
     default: {
       NS_WARNING("Unsupported SurfaceDescriptor type for VR layer texture");
       return;
     }
   }
 
@@ -366,16 +361,45 @@ VRDisplayHost::SubmitFrame(VRLayerParent
 
   loop->PostTask(NewRunnableMethod<const uint32_t>(
     "gfx::VRManager::NotifyVRVsync",
     vm, &VRManager::NotifyVRVsync, mDisplayInfo.mDisplayID
   ));
 #endif
 }
 
+void
+VRDisplayHost::SubmitFrame(VRLayerParent* aLayer,
+                           const layers::SurfaceDescriptor &aTexture,
+                           uint64_t aFrameId,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect)
+{
+  if (!mSubmitThread) {
+    mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame"));
+  }
+
+  if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
+    // Suppress layers hidden by the group mask
+    return;
+  }
+
+  // Ensure that we only accept the first SubmitFrame call per RAF cycle.
+  if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
+    return;
+  }
+
+  mSubmitThread->Start();
+  mSubmitThread->PostTask(
+    NewRunnableMethod<StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
+      StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
+      "gfx::VRDisplayHost::SubmitFrameInternal", this, &VRDisplayHost::SubmitFrameInternal,
+      aTexture, aFrameId, aLeftEyeRect, aRightEyeRect));
+}
+
 bool
 VRDisplayHost::CheckClearDisplayInfoDirty()
 {
   if (mDisplayInfo == mLastUpdateDisplayInfo) {
     return false;
   }
   mLastUpdateDisplayInfo = mDisplayInfo;
   return true;
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -22,16 +22,17 @@
 
 #if defined(XP_WIN)
 #include <d3d11_1.h>
 #elif defined(XP_MACOSX)
 class MacIOSurface;
 #endif
 namespace mozilla {
 namespace gfx {
+class VRThread;
 class VRLayerParent;
 
 class VRDisplayHost {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayHost)
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
 
@@ -95,17 +96,23 @@ protected:
 
   nsTArray<VRLayerParent *> mLayers;
   // Weak reference to mLayers entries are cleared in
   // VRLayerParent destructor
 
 protected:
   virtual VRHMDSensorState GetSensorState() = 0;
 
+  RefPtr<VRThread> mSubmitThread;
 private:
+  void SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
+                           uint64_t aFrameId,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect);
+
   VRDisplayInfo mLastUpdateDisplayInfo;
   TimeStamp mLastFrameStart;
   bool mFrameStarted;
 
 #if defined(XP_WIN)
 protected:
   bool CreateD3DObjects();
   RefPtr<ID3D11Device1> mDevice;
--- a/gfx/vr/VRThread.cpp
+++ b/gfx/vr/VRThread.cpp
@@ -1,24 +1,25 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-
 #include "VRThread.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
 namespace gfx {
 
 static StaticRefPtr<VRListenerThreadHolder> sVRListenerThreadHolder;
 static bool sFinishedVRListenerShutDown = false;
+static const uint32_t kDefaultThreadLifeTime = 60; // in 60 seconds.
+static const uint32_t kDelayPostTaskTime = 20000; // in 20000 ms.
 
 VRListenerThreadHolder* GetVRListenerThreadHolder()
 {
   return sVRListenerThreadHolder;
 }
 
 base::Thread*
 VRListenerThread()
@@ -109,16 +110,133 @@ VRListenerThreadHolder::Shutdown()
 
 /* static */ bool
 VRListenerThreadHolder::IsInVRListenerThread()
 {
   return VRListenerThread() &&
 		 VRListenerThread()->thread_id() == PlatformThread::CurrentId();
 }
 
-} // namespace gfx
-} // namespace mozilla
+VRThread::VRThread(const nsCString& aName)
+ : mThread(nullptr)
+ , mLifeTime(kDefaultThreadLifeTime)
+ , mStarted(false)
+{
+  mName = aName;
+}
+
+VRThread::~VRThread()
+{
+  Shutdown();
+}
+
+void
+VRThread::Start()
+{
+  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
+
+  if (!mThread) {
+    nsresult rv = NS_NewNamedThread(mName, getter_AddRefs(mThread));
+    MOZ_ASSERT(mThread);
+
+    if (NS_FAILED(rv)) {
+      MOZ_ASSERT(false, "Failed to create a vr thread.");
+    }
+    RefPtr<Runnable> runnable =
+      NewRunnableMethod<TimeStamp>(
+        "gfx::VRThread::CheckLife", this, &VRThread::CheckLife, TimeStamp::Now());
+    // Post it to the main thread for tracking the lifetime.
+    nsCOMPtr<nsIThread> mainThread;
+    rv = NS_GetMainThread(getter_AddRefs(mainThread));
+    if (NS_FAILED(rv)) {
+      NS_WARNING("VRThread::Start() could not get Main thread");
+      return;
+    }
+    mainThread->DelayedDispatch(runnable.forget(), kDelayPostTaskTime);
+  }
+  mStarted = true;
+  mLastActiveTime = TimeStamp::Now();
+}
+
+void
+VRThread::Shutdown()
+{
+  if (mThread) {
+    mThread->Shutdown();
+    mThread = nullptr;
+  }
+  mStarted = false;
+}
+
+const nsCOMPtr<nsIThread>
+VRThread::GetThread() const
+{
+  return mThread;
+}
+
+void
+VRThread::PostTask(already_AddRefed<Runnable> aTask)
+{
+  PostDelayedTask(Move(aTask), 0);
+}
+
+void
+VRThread::PostDelayedTask(already_AddRefed<Runnable> aTask,
+                          uint32_t aTime)
+{
+  MOZ_ASSERT(mStarted, "Must call Start() before posting tasks.");
+  MOZ_ASSERT(mThread);
+  mLastActiveTime = TimeStamp::Now();
+
+  if (!aTime) {
+    mThread->Dispatch(Move(aTask), NS_DISPATCH_NORMAL);
+  } else {
+    mThread->DelayedDispatch(Move(aTask), aTime);
+  }
+}
+
+void
+VRThread::CheckLife(TimeStamp aCheckTimestamp)
+{
+  // VR system is going to shutdown.
+  if (!mStarted) {
+    Shutdown();
+    return;
+  }
+
+  const TimeDuration timeout = TimeDuration::FromSeconds(mLifeTime);
+  if ((aCheckTimestamp - mLastActiveTime) > timeout) {
+    Shutdown();
+  } else {
+    RefPtr<Runnable> runnable =
+      NewRunnableMethod<TimeStamp>(
+        "gfx::VRThread::CheckLife", this, &VRThread::CheckLife, TimeStamp::Now());
+    // Post it to the main thread for tracking the lifetime.
+    nsCOMPtr<nsIThread> mainThread;
+    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+    if (NS_FAILED(rv)) {
+      NS_WARNING("VRThread::CheckLife() could not get Main thread");
+      return;
+    }
+    mainThread->DelayedDispatch(runnable.forget(), kDelayPostTaskTime);
+  }
+}
+
+void
+VRThread::SetLifeTime(uint32_t aLifeTime)
+{
+  mLifeTime = aLifeTime;
+}
+
+uint32_t
+VRThread::GetLifeTime()
+{
+  return mLifeTime;
+}
 
 bool
-NS_IsInVRListenerThread()
+VRThread::IsActive()
 {
-  return mozilla::gfx::VRListenerThreadHolder::IsInVRListenerThread();
-}
\ No newline at end of file
+  return !!mThread;
+}
+
+} // namespace gfx
+} // namespace mozilla
--- a/gfx/vr/VRThread.h
+++ b/gfx/vr/VRThread.h
@@ -41,12 +41,40 @@ private:
   base::Thread* const mThread;
 
   static base::Thread* CreateThread();
   static void DestroyThread(base::Thread* aThread);
 };
 
 base::Thread* VRListenerThread();
 
+class VRThread final
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRThread)
+
+public:
+  explicit VRThread(const nsCString& aName);
+
+  void Start();
+  void Shutdown();
+  void SetLifeTime(uint32_t aLifeTime);
+  uint32_t GetLifeTime();
+  void CheckLife(TimeStamp aCheckTimestamp);
+  void PostTask(already_AddRefed<Runnable> aTask);
+  void PostDelayedTask(already_AddRefed<Runnable> aTask, uint32_t aTime);
+  const nsCOMPtr<nsIThread> GetThread() const;
+  bool IsActive();
+
+protected:
+  ~VRThread();
+
+private:
+  nsCOMPtr<nsIThread> mThread;
+  TimeStamp mLastActiveTime;
+  nsCString mName;
+  uint32_t mLifeTime;
+  Atomic<bool> mStarted;
+};
+
 } // namespace gfx
 } // namespace mozilla
 
 #endif // GFX_VR_THREAD_H
\ No newline at end of file
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -349,27 +349,29 @@ VRDisplayOSVR::SubmitFrame(ID3D11Texture
 
 bool
 VRDisplayOSVR::SubmitFrame(MacIOSurface* aMacIOSurface,
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect)
 {
   // XXX Add code to submit frame
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   return false;
 }
 
 #elif defined(MOZ_ANDROID_GOOGLE_VR)
 
 bool
 VRDisplayOSVR::SubmitFrame(const mozilla::layers::EGLImageDescriptor*,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect)
 {
   // XXX Add code to submit frame
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   return false;
 }
 
 #endif
 
 void
 VRDisplayOSVR::StartPresentation()
 {
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -303,16 +303,17 @@ VROculusSession::StopPresentation()
       }
     }
     Refresh();
   }
 }
 
 VROculusSession::~VROculusSession()
 {
+  mSubmitThread = nullptr;
   Uninitialize(true);
 }
 
 void
 VROculusSession::Uninitialize(bool aUnloadLib)
 {
   StopRendering();
   StopSession();
@@ -382,26 +383,29 @@ VROculusSession::Refresh(bool aForceRefr
       // ensures that the user will not drop to Oculus Home during VR link
       // traversal.
       bInvisible = false;
 
       // While we are waiting for either the timeout or a new presentation,
       // fill the HMD with black / no layers.
       if (mSession && mTextureSet) {
         if (!aForceRefresh) {
-          // ovr_SubmitFrame is only allowed been run at Compositor thread,
-          // so we post this task to Compositor thread and let it determine
-          // if reloading library.
+          // VROculusSession didn't start submitting frames yet.
+          if (!mSubmitThread) {
+            return;
+          }
+          // ovr_SubmitFrame is running at VR Submit thread,
+          // so we post this task to VR Submit thread and let it paint
+          // a black frame.
           mDrawBlack = true;
-          MessageLoop* loop = layers::CompositorThreadHolder::Loop();
-          loop->PostTask(NewRunnableMethod<bool>(
+          MOZ_ASSERT(mSubmitThread->IsActive());
+          mSubmitThread->PostTask(NewRunnableMethod<bool>(
             "gfx::VROculusSession::Refresh",
             this,
             &VROculusSession::Refresh, true));
-
           return;
         }
         ovrLayerEyeFov layer;
         memset(&layer, 0, sizeof(layer));
         layer.Header.Type = ovrLayerType_Disabled;
         ovrLayerHeader *layers = &layer.Header;
         ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1);
         mDrawBlack = false;
@@ -1131,16 +1135,17 @@ VRDisplayOculus::UpdateConstantBuffers()
 }
 
 bool
 VRDisplayOculus::SubmitFrame(ID3D11Texture2D* aSource,
                              const IntSize& aSize,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   if (!CreateD3DObjects()) {
     return false;
   }
 
   AutoRestoreRenderState restoreState(this);
   if (!restoreState.IsSuccess()) {
     return false;
   }
@@ -1281,16 +1286,17 @@ VRDisplayOculus::SubmitFrame(ID3D11Textu
      * "health and safety warning", orv will be > 0 (OVR_SUCCESS but not
      * OVR_UNQUALIFIED_SUCCESS) and ovr_SubmitFrame will not block.
      * In this case, returning true would have resulted in an unthrottled
      * render loop hiting excessive frame rates and consuming resources.
      */
     return false;
   }
 
+  mSession->mSubmitThread = mSubmitThread;
   return true;
 }
 
 void
 VRDisplayOculus::Refresh()
 {
   mDisplayInfo.mIsConnected = mSession->IsTrackingReady();
   mDisplayInfo.mIsMounted = mSession->IsMounted();
@@ -1375,17 +1381,17 @@ void
 VRControllerOculus::UpdateVibrateHaptic(ovrSession aSession,
                                         uint32_t aHapticIndex,
                                         double aIntensity,
                                         double aDuration,
                                         uint64_t aVibrateIndex,
                                         const VRManagerPromise& aPromise)
 {
   // UpdateVibrateHaptic() only can be called by mVibrateThread
-  MOZ_ASSERT(mVibrateThread == NS_GetCurrentThread());
+  MOZ_ASSERT(mVibrateThread->GetThread() == NS_GetCurrentThread());
 
   // It has been interrupted by loss focus.
   if (mIsVibrateStopped) {
     VibrateHapticComplete(aSession, aPromise, true);
     return;
   }
   // Avoid the previous vibrate event to override the new one.
   if (mVibrateIndex != aVibrateIndex) {
@@ -1434,18 +1440,17 @@ VRControllerOculus::UpdateVibrateHaptic(
     MOZ_ASSERT(mVibrateThread);
 
     RefPtr<Runnable> runnable =
       NewRunnableMethod<ovrSession, uint32_t, double, double, uint64_t,
         StoreCopyPassByConstLRef<VRManagerPromise>>(
           "VRControllerOculus::UpdateVibrateHaptic",
           this, &VRControllerOculus::UpdateVibrateHaptic, aSession,
           aHapticIndex, aIntensity, (duration > kVibrateRate) ? remainingTime : 0, aVibrateIndex, aPromise);
-    NS_DelayedDispatchToCurrentThread(runnable.forget(),
-                                      (duration > kVibrateRate) ? kVibrateRate : remainingTime);
+    mVibrateThread->PostDelayedTask(runnable.forget(), (duration > kVibrateRate) ? kVibrateRate : remainingTime);
   } else {
     VibrateHapticComplete(aSession, aPromise, true);
   }
 }
 
 void
 VRControllerOculus::VibrateHapticComplete(ovrSession aSession, const VRManagerPromise& aPromise,
                                           bool aStop)
@@ -1485,33 +1490,29 @@ void
 VRControllerOculus::VibrateHaptic(ovrSession aSession,
                                   uint32_t aHapticIndex,
                                   double aIntensity,
                                   double aDuration,
                                   const VRManagerPromise& aPromise)
 {
   // Spinning up the haptics thread at the first haptics call.
   if (!mVibrateThread) {
-    nsresult rv = NS_NewThread(getter_AddRefs(mVibrateThread));
-    MOZ_ASSERT(mVibrateThread);
-
-    if (NS_FAILED(rv)) {
-      MOZ_ASSERT(false, "Failed to create async thread.");
-    }
+    mVibrateThread = new VRThread(NS_LITERAL_CSTRING("Oculus_Vibration"));
   }
+  mVibrateThread->Start();
   ++mVibrateIndex;
   mIsVibrateStopped = false;
 
   RefPtr<Runnable> runnable =
-       NewRunnableMethod<ovrSession, uint32_t, double, double, uint64_t,
-        StoreCopyPassByConstLRef<VRManagerPromise>>(
-          "VRControllerOculus::UpdateVibrateHaptic",
-          this, &VRControllerOculus::UpdateVibrateHaptic, aSession,
-          aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise);
-  mVibrateThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+    NewRunnableMethod<ovrSession, uint32_t, double, double, uint64_t,
+      StoreCopyPassByConstLRef<VRManagerPromise>>(
+        "VRControllerOculus::UpdateVibrateHaptic",
+        this, &VRControllerOculus::UpdateVibrateHaptic, aSession,
+        aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise);
+  mVibrateThread->PostTask(runnable.forget());
 }
 
 void
 VRControllerOculus::StopVibrateHaptic()
 {
   mIsVibrateStopped = true;
 }
 
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -22,16 +22,18 @@ struct ID3D11Device;
 
 namespace mozilla {
 namespace layers {
 class CompositingRenderTargetD3D11;
 struct VertexShaderConstants;
 struct PixelShaderConstants;
 }
 namespace gfx {
+class VRThread;
+
 namespace impl {
 
 enum class OculusControllerAxisType : uint16_t {
   ThumbstickXAxis,
   ThumbstickYAxis,
   NumVRControllerAxisType
 };
 
@@ -57,16 +59,17 @@ public:
 private:
   PRLibrary* mOvrLib;
   ovrSession mSession;
   ovrInitFlags mInitFlags;
   ovrTextureSwapChain mTextureSet;
   nsTArray<RefPtr<layers::CompositingRenderTargetD3D11>> mRenderTargets;
   IntSize mPresentationSize;
   RefPtr<ID3D11Device> mDevice;
+  RefPtr<VRThread> mSubmitThread;
   // The timestamp of the last time Oculus set ShouldQuit to true.
   TimeStamp mLastShouldQuit;
   // The timestamp of the last ending presentation
   TimeStamp mLastPresentationEnd;
   VRTelemetry mTelemetry;
   bool mRequestPresentation;
   bool mRequestTracking;
   bool mTracking;
@@ -165,17 +168,17 @@ private:
                            uint64_t aVibrateIndex,
                            const VRManagerPromise& aPromise);
   void VibrateHapticComplete(ovrSession aSession, const VRManagerPromise& aPromise, bool aStop);
 
   float mAxisMove[static_cast<uint32_t>(
                   OculusControllerAxisType::NumVRControllerAxisType)];
   float mIndexTrigger;
   float mHandTrigger;
-  nsCOMPtr<nsIThread> mVibrateThread;
+  RefPtr<VRThread> mVibrateThread;
   Atomic<bool> mIsVibrateStopped;
 };
 
 } // namespace impl
 
 class VRSystemManagerOculus : public VRSystemManager
 {
 public:
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -348,16 +348,17 @@ VRDisplayOpenVR::StopPresentation()
 
 bool
 VRDisplayOpenVR::SubmitFrame(void* aTextureHandle,
                              ::vr::ETextureType aTextureType,
                              const IntSize& aSize,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   if (!mIsPresenting) {
     return false;
   }
 
   ::vr::Texture_t tex;
   tex.handle = aTextureHandle;
   tex.eType = aTextureType;
   tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
@@ -498,17 +499,17 @@ void
 VRControllerOpenVR::UpdateVibrateHaptic(::vr::IVRSystem* aVRSystem,
                                         uint32_t aHapticIndex,
                                         double aIntensity,
                                         double aDuration,
                                         uint64_t aVibrateIndex,
                                         const VRManagerPromise& aPromise)
 {
   // UpdateVibrateHaptic() only can be called by mVibrateThread
-  MOZ_ASSERT(mVibrateThread == NS_GetCurrentThread());
+  MOZ_ASSERT(mVibrateThread->GetThread() == NS_GetCurrentThread());
 
   // It has been interrupted by loss focus.
   if (mIsVibrateStopped) {
     VibrateHapticComplete(aPromise);
     return;
   }
   // Avoid the previous vibrate event to override the new one.
   if (mVibrateIndex != aVibrateIndex) {
@@ -524,24 +525,25 @@ VRControllerOpenVR::UpdateVibrateHaptic(
   aVRSystem->TriggerHapticPulse(GetTrackedIndex(),
                                 aHapticIndex, microSec);
 
   // In OpenVR spec, it mentions TriggerHapticPulse() may not trigger another haptic pulse
   // on this controller and axis combination for 5ms.
   const double kVibrateRate = 5.0;
   if (duration >= kVibrateRate) {
     MOZ_ASSERT(mVibrateThread);
+    MOZ_ASSERT(mVibrateThread->IsActive());
 
     RefPtr<Runnable> runnable =
       NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t,
         StoreCopyPassByConstLRef<VRManagerPromise>>(
           "VRControllerOpenVR::UpdateVibrateHaptic",
           this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
           aHapticIndex, aIntensity, duration - kVibrateRate, aVibrateIndex, aPromise);
-    NS_DelayedDispatchToCurrentThread(runnable.forget(), kVibrateRate);
+    mVibrateThread->PostDelayedTask(runnable.forget(), kVibrateRate);
   } else {
     // The pulse has completed
     VibrateHapticComplete(aPromise);
   }
 }
 
 void
 VRControllerOpenVR::VibrateHapticComplete(const VRManagerPromise& aPromise)
@@ -557,33 +559,29 @@ void
 VRControllerOpenVR::VibrateHaptic(::vr::IVRSystem* aVRSystem,
                                   uint32_t aHapticIndex,
                                   double aIntensity,
                                   double aDuration,
                                   const VRManagerPromise& aPromise)
 {
   // Spinning up the haptics thread at the first haptics call.
   if (!mVibrateThread) {
-    nsresult rv = NS_NewThread(getter_AddRefs(mVibrateThread));
-    MOZ_ASSERT(mVibrateThread);
-
-    if (NS_FAILED(rv)) {
-      MOZ_ASSERT(false, "Failed to create async thread.");
-    }
+    mVibrateThread = new VRThread(NS_LITERAL_CSTRING("OpenVR_Vibration"));
   }
+  mVibrateThread->Start();
   ++mVibrateIndex;
   mIsVibrateStopped = false;
 
   RefPtr<Runnable> runnable =
-      NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t,
-        StoreCopyPassByConstLRef<VRManagerPromise>>(
-          "VRControllerOpenVR::UpdateVibrateHaptic",
-          this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
-          aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise);
-  mVibrateThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+    NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t,
+      StoreCopyPassByConstLRef<VRManagerPromise>>(
+        "VRControllerOpenVR::UpdateVibrateHaptic",
+        this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
+        aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise);
+  mVibrateThread->PostTask(runnable.forget());
 }
 
 void
 VRControllerOpenVR::StopVibrateHaptic()
 {
   mIsVibrateStopped = true;
 }
 
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -4,32 +4,33 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_VR_OPENVR_H
 #define GFX_VR_OPENVR_H
 
 #include "nsTArray.h"
 #include "nsIScreen.h"
-#include "nsIThread.h"
 #include "nsCOMPtr.h"
 #include "mozilla/RefPtr.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/EnumeratedArray.h"
 
 #include "openvr.h"
 #include "gfxVR.h"
 #include "VRDisplayHost.h"
 
 #if defined(XP_MACOSX)
 class MacIOSurface;
 #endif
 namespace mozilla {
 namespace gfx {
+class VRThread;
+
 namespace impl {
 
 class VRDisplayOpenVR : public VRDisplayHost
 {
 public:
   void ZeroSensor() override;
   bool GetIsHmdPresent();
 
@@ -107,17 +108,17 @@ private:
                            uint64_t aVibrateIndex,
                            const VRManagerPromise& aPromise);
   void VibrateHapticComplete(const VRManagerPromise& aPromise);
 
   // The index of tracked devices from ::vr::IVRSystem.
   uint32_t mTrackedIndex;
   nsTArray<float> mTrigger;
   nsTArray<float> mAxisMove;
-  nsCOMPtr<nsIThread> mVibrateThread;
+  RefPtr<VRThread> mVibrateThread;
   Atomic<bool> mIsVibrateStopped;
 };
 
 } // namespace impl
 
 class VRSystemManagerOpenVR : public VRSystemManager
 {
 public:
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -284,16 +284,17 @@ VRDisplayPuppet::UpdateConstantBuffers()
 }
 
 bool
 VRDisplayPuppet::SubmitFrame(ID3D11Texture2D* aSource,
                              const IntSize& aSize,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   if (!mIsPresenting) {
     return false;
   }
 
   if (!CreateD3DObjects()) {
     return false;
   }
   AutoRestoreRenderState restoreState(this);
@@ -480,16 +481,17 @@ VRDisplayPuppet::SubmitFrame(ID3D11Textu
 #elif defined(XP_MACOSX)
 
 bool
 VRDisplayPuppet::SubmitFrame(MacIOSurface* aMacIOSurface,
                              const IntSize& aSize,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   if (!mIsPresenting || !aMacIOSurface) {
     return false;
   }
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
 
   switch (gfxPrefs::VRPuppetSubmitFrame()) {
@@ -553,18 +555,19 @@ VRDisplayPuppet::SubmitFrame(MacIOSurfac
   return false;
 }
 
 #elif defined(MOZ_ANDROID_GOOGLE_VR)
 
 bool
 VRDisplayPuppet::SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
                            const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) {
-
+                           const gfx::Rect& aRightEyeRect)
+{
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
   return false;
 }
 
 #endif
 
 void
 VRDisplayPuppet::Refresh()
 {
--- a/gfx/vr/ipc/VRLayerParent.cpp
+++ b/gfx/vr/ipc/VRLayerParent.cpp
@@ -60,36 +60,20 @@ VRLayerParent::Destroy()
 
 mozilla::ipc::IPCResult
 VRLayerParent::RecvSubmitFrame(const layers::SurfaceDescriptor &aTexture,
                                const uint64_t& aFrameId,
                                const gfx::Rect& aLeftEyeRect,
                                const gfx::Rect& aRightEyeRect)
 {
   if (mVRDisplayID) {
-    MessageLoop* loop = layers::CompositorThreadHolder::Loop();
     VRManager* vm = VRManager::Get();
     RefPtr<VRDisplayHost> display = vm->GetDisplay(mVRDisplayID);
     if (display) {
-      // Because VR compositor still shares the same graphics device with Compositor thread.
-      // We have to post sumbit frame tasks to Compositor thread.
-      // TODO: Move SubmitFrame to Bug 1392217.
-      loop->PostTask(NewRunnableMethod<VRDisplayHost*, const layers::SurfaceDescriptor, uint64_t,
-                                       const gfx::Rect&, const gfx::Rect&>(
-                     "gfx::VRLayerParent::SubmitFrame",
-                     this,
-                     &VRLayerParent::SubmitFrame, display, aTexture, aFrameId, aLeftEyeRect, aRightEyeRect));
+      display->SubmitFrame(this, aTexture, aFrameId, aLeftEyeRect, aRightEyeRect);
     }
   }
 
   return IPC_OK();
 }
 
-void
-VRLayerParent::SubmitFrame(VRDisplayHost* aDisplay, const layers::SurfaceDescriptor& aTexture,
-                           uint64_t aFrameId, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect)
-{
-  aDisplay->SubmitFrame(this, aTexture, aFrameId,
-                        aLeftEyeRect, aRightEyeRect);
-}
-
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/ipc/VRLayerParent.h
+++ b/gfx/vr/ipc/VRLayerParent.h
@@ -35,18 +35,14 @@ protected:
   void Destroy();
 
   bool mIPCOpen;
 
   uint32_t mVRDisplayID;
   gfx::Rect mLeftEyeRect;
   gfx::Rect mRightEyeRect;
   uint32_t mGroup;
-
-private:
-  void SubmitFrame(VRDisplayHost* aDisplay, const layers::SurfaceDescriptor& aTexture,
-                   uint64_t aFrameId, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect);
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -14,25 +14,26 @@ debugger = ["ws", "serde_json", "serde",
 
 [dependencies]
 app_units = "0.5.6"
 bincode = "0.9"
 byteorder = "1.0"
 euclid = "0.15.5"
 fxhash = "0.2.1"
 gleam = "0.4.15"
-lazy_static = "0.2"
+lazy_static = "1"
 log = "0.3"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.6"
+smallvec = "0.5"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
 serde = { optional = true, version = "1.0" }
 serde_derive = { optional = true, version = "1.0" }
 image = { optional = true, version = "0.17" }
 base64 = { optional = true, version = "0.3.0" }
 
 [dev-dependencies]
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -9,99 +9,196 @@ void brush_vs(
     vec2 local_pos,
     RectWithSize local_rect,
     ivec2 user_data
 );
 
 #define RASTERIZATION_MODE_LOCAL_SPACE      0.0
 #define RASTERIZATION_MODE_SCREEN_SPACE     1.0
 
+#define SEGMENT_ALL             0
+#define SEGMENT_TOP_LEFT        1
+#define SEGMENT_TOP_RIGHT       2
+#define SEGMENT_BOTTOM_RIGHT    3
+#define SEGMENT_BOTTOM_LEFT     4
+#define SEGMENT_TOP_MID         5
+#define SEGMENT_MID_RIGHT       6
+#define SEGMENT_BOTTOM_MID      7
+#define SEGMENT_MID_LEFT        8
+#define SEGMENT_CENTER          9
+
+#define AA_KIND_DEFAULT         0
+#define AA_KIND_SEGMENT         1
+
+#define VECS_PER_BRUSH_PRIM                 4
+
 struct BrushInstance {
     int picture_address;
     int prim_address;
     int clip_node_id;
     int scroll_node_id;
     int clip_address;
     int z;
-    int flags;
+    int segment_kind;
     ivec2 user_data;
 };
 
 BrushInstance load_brush() {
 	BrushInstance bi;
 
     bi.picture_address = aData0.x;
     bi.prim_address = aData0.y;
     bi.clip_node_id = aData0.z / 65536;
     bi.scroll_node_id = aData0.z % 65536;
     bi.clip_address = aData0.w;
     bi.z = aData1.x;
-    bi.flags = aData1.y;
+    bi.segment_kind = aData1.y;
     bi.user_data = aData1.zw;
 
     return bi;
 }
 
+struct BrushPrimitive {
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
+    vec4 offsets;
+    int aa_kind;
+};
+
+BrushPrimitive fetch_brush_primitive(int address) {
+    vec4 data[4] = fetch_from_resource_cache_4(address);
+
+    BrushPrimitive prim = BrushPrimitive(
+        RectWithSize(data[0].xy, data[0].zw),
+        RectWithSize(data[1].xy, data[1].zw),
+        data[2],
+        int(data[3].x)
+    );
+
+    return prim;
+}
+
 void main(void) {
     // Load the brush instance from vertex attributes.
     BrushInstance brush = load_brush();
 
     // Load the geometry for this brush. For now, this is simply the
     // local rect of the primitive. In the future, this will support
     // loading segment rects, and other rect formats (glyphs).
-    PrimitiveGeometry geom = fetch_primitive_geometry(brush.prim_address);
+    BrushPrimitive brush_prim = fetch_brush_primitive(brush.prim_address);
+
+    // Fetch the segment of this brush primitive we are drawing.
+    RectWithSize local_segment_rect;
+    vec4 edge_aa_segment_mask;
+
+    // p0 = origin of outer rect
+    // p1 = origin of inner rect
+    // p2 = bottom right corner of inner rect
+    // p3 = bottom right corner of outer rect
+    vec2 p0 = brush_prim.local_rect.p0;
+    vec2 p1 = brush_prim.local_rect.p0 + brush_prim.offsets.xy;
+    vec2 p2 = brush_prim.local_rect.p0 + brush_prim.local_rect.size - brush_prim.offsets.zw;
+    vec2 p3 = brush_prim.local_rect.p0 + brush_prim.local_rect.size;
+
+    switch (brush.segment_kind) {
+        case SEGMENT_ALL:
+            local_segment_rect = brush_prim.local_rect;
+            break;
+
+        case SEGMENT_TOP_LEFT:
+            local_segment_rect = RectWithSize(p0, p1 - p0);
+            break;
+        case SEGMENT_TOP_RIGHT:
+            local_segment_rect = RectWithSize(vec2(p2.x, p0.y), vec2(p3.x - p2.x, p1.y - p0.y));
+            break;
+        case SEGMENT_BOTTOM_RIGHT:
+            local_segment_rect = RectWithSize(vec2(p2.x, p2.y), vec2(p3.x - p2.x, p3.y - p2.y));
+            break;
+        case SEGMENT_BOTTOM_LEFT:
+            local_segment_rect = RectWithSize(vec2(p0.x, p2.y), vec2(p1.x - p0.x, p3.y - p2.y));
+            break;
+
+        case SEGMENT_TOP_MID:
+            local_segment_rect = RectWithSize(vec2(p1.x, p0.y), vec2(p2.x - p1.x, p1.y - p0.y));
+            break;
+        case SEGMENT_MID_RIGHT:
+            local_segment_rect = RectWithSize(vec2(p2.x, p1.y), vec2(p3.x - p2.x, p2.y - p1.y));
+            break;
+        case SEGMENT_BOTTOM_MID:
+            local_segment_rect = RectWithSize(vec2(p1.x, p2.y), vec2(p2.x - p1.x, p3.y - p2.y));
+            break;
+        case SEGMENT_MID_LEFT:
+            local_segment_rect = RectWithSize(vec2(p0.x, p1.y), vec2(p1.x - p0.x, p2.y - p1.y));
+            break;
+
+        case SEGMENT_CENTER:
+            local_segment_rect = RectWithSize(p1, p2 - p1);
+            break;
+
+        default:
+            local_segment_rect = RectWithSize(vec2(0.0), vec2(0.0));
+            break;
+    }
+
+    switch (brush_prim.aa_kind) {
+        case AA_KIND_SEGMENT:
+            // TODO: select these correctly based on the segment kind.
+            edge_aa_segment_mask = vec4(1.0);
+            break;
+        case AA_KIND_DEFAULT:
+            edge_aa_segment_mask = vec4(1.0);
+            break;
+    }
 
     vec2 device_pos, local_pos;
-    RectWithSize local_rect = geom.local_rect;
 
     // Fetch the dynamic picture that we are drawing on.
     PictureTask pic_task = fetch_picture_task(brush.picture_address);
 
     if (pic_task.rasterization_mode == RASTERIZATION_MODE_LOCAL_SPACE) {
-
-        local_pos = local_rect.p0 + aPosition.xy * local_rect.size;
+        local_pos = local_segment_rect.p0 + aPosition.xy * local_segment_rect.size;
 
         // Right now - pictures only support local positions. In the future, this
         // will be expanded to support transform picture types (the common kind).
         device_pos = pic_task.common_data.task_rect.p0 +
                      uDevicePixelRatio * (local_pos - pic_task.content_origin);
 
         // Write the final position transformed by the orthographic device-pixel projection.
         gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
     } else {
         VertexInfo vi;
         Layer layer = fetch_layer(brush.clip_node_id, brush.scroll_node_id);
         ClipArea clip_area = fetch_clip_area(brush.clip_address);
 
         // Write the normal vertex information out.
         if (layer.is_axis_aligned) {
             vi = write_vertex(
-                geom.local_rect,
-                geom.local_clip_rect,
+                local_segment_rect,
+                brush_prim.local_clip_rect,
                 float(brush.z),
                 layer,
                 pic_task,
-                geom.local_rect
+                brush_prim.local_rect
             );
 
             // TODO(gw): vLocalBounds may be referenced by
             //           the fragment shader when running in
             //           the alpha pass, even on non-transformed
             //           items. For now, just ensure it has no
             //           effect. We can tidy this up as we move
             //           more items to be brush shaders.
-            vLocalBounds = vec4(
-                geom.local_clip_rect.p0,
-                geom.local_clip_rect.p0 + geom.local_clip_rect.size
-            );
+#ifdef WR_FEATURE_ALPHA_PASS
+            vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
+#endif
         } else {
-            vi = write_transform_vertex(geom.local_rect,
-                geom.local_rect,
-                geom.local_clip_rect,
-                vec4(1.0),
+            vi = write_transform_vertex(
+                local_segment_rect,
+                brush_prim.local_rect,
+                brush_prim.local_clip_rect,
+                edge_aa_segment_mask,
                 float(brush.z),
                 layer,
                 pic_task
             );
         }
 
         local_pos = vi.local_pos;
 
@@ -116,19 +213,19 @@ void main(void) {
             vi.screen_pos,
             clip_area
         );
 #endif
     }
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
-        brush.prim_address + VECS_PER_PRIM_HEADER,
+        brush.prim_address + VECS_PER_BRUSH_PRIM,
         local_pos,
-        local_rect,
+        brush_prim.local_rect,
         brush.user_data
     );
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 
 vec4 brush_fs();
--- a/gfx/webrender/res/brush_mask_corner.glsl
+++ b/gfx/webrender/res/brush_mask_corner.glsl
@@ -46,15 +46,16 @@ vec4 brush_fs() {
     //       since otherwise the results can be undefined if the
     //       input function is not continuous. I have observed this
     //       as flickering behaviour on Intel GPUs.
     float aa_range = compute_aa_range(vLocalPos);
     // Check if in valid clip region.
     if (vLocalPos.x < vClipCenter_Radius.x && vLocalPos.y < vClipCenter_Radius.y) {
         // Apply ellipse clip on corner.
         d = distance_to_ellipse(vLocalPos - vClipCenter_Radius.xy,
-                                vClipCenter_Radius.zw);
+                                vClipCenter_Radius.zw,
+                                aa_range);
         d = distance_aa(aa_range, d);
     }
 
     return vec4(mix(d, 1.0 - d, vClipMode));
 }
 #endif
--- a/gfx/webrender/res/brush_mask_rounded_rect.glsl
+++ b/gfx/webrender/res/brush_mask_rounded_rect.glsl
@@ -9,45 +9,45 @@ flat varying vec4 vClipCenter_Radius_TL;
 flat varying vec4 vClipCenter_Radius_TR;
 flat varying vec4 vClipCenter_Radius_BR;
 flat varying vec4 vClipCenter_Radius_BL;
 flat varying vec4 vLocalRect;
 varying vec2 vLocalPos;
 
 #ifdef WR_VERTEX_SHADER
 
-struct BrushPrimitive {
+struct RoundedRectPrimitive {
     float clip_mode;
     vec4 rect;
     vec2 radius_tl;
     vec2 radius_tr;
     vec2 radius_br;
     vec2 radius_bl;
 };
 
-BrushPrimitive fetch_brush_primitive(int address) {
+RoundedRectPrimitive fetch_rounded_rect_primitive(int address) {
     vec4 data[4] = fetch_from_resource_cache_4(address);
-    return BrushPrimitive(
+    return RoundedRectPrimitive(
         data[0].x,
         data[1],
         data[2].xy,
         data[2].zw,
         data[3].xy,
         data[3].zw
     );
 }
 
 void brush_vs(
     int prim_address,
     vec2 local_pos,
     RectWithSize local_rect,
     ivec2 user_data
 ) {
     // Load the specific primitive.
-    BrushPrimitive prim = fetch_brush_primitive(prim_address);
+    RoundedRectPrimitive prim = fetch_rounded_rect_primitive(prim_address);
 
     // Write clip parameters
     vClipMode = prim.clip_mode;
 
     // TODO(gw): In the future, when brush primitives may be segment rects
     //           we need to account for that here, and differentiate between
     //           the segment rect (geometry) amd the primitive rect (which
     //           defines where the clip radii are relative to).
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/brush_solid.glsl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,prim_shared,brush
+
+flat varying vec4 vColor;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+struct SolidBrush {
+    vec4 color;
+};
+
+SolidBrush fetch_solid_primitive(int address) {
+    vec4 data = fetch_from_resource_cache_1(address);
+    return SolidBrush(data);
+}
+
+void brush_vs(
+    int prim_address,
+    vec2 local_pos,
+    RectWithSize local_rect,
+    ivec2 user_data
+) {
+    SolidBrush prim = fetch_solid_primitive(prim_address);
+    vColor = prim.color;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    vLocalPos = local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec4 brush_fs() {
+    vec4 color = vColor;
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(vLocalPos);
+#endif
+    return color;
+}
+#endif
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -45,48 +45,18 @@ RectWithSize intersect_rect(RectWithSize
     vec4 p = clamp(vec4(a.p0, a.p0 + a.size), b.p0.xyxy, b.p0.xyxy + b.size.xyxy);
     return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
 }
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       Layer layer,
-                                      ClipArea area,
-                                      int segment) {
-    vec2 outer_p0 = area.screen_origin;
-    vec2 outer_p1 = outer_p0 + area.common_data.task_rect.size;
-    vec2 inner_p0 = area.inner_rect.xy;
-    vec2 inner_p1 = area.inner_rect.zw;
-
-    vec2 p0, p1;
-    switch (segment) {
-        case SEGMENT_ALL:
-            p0 = outer_p0;
-            p1 = outer_p1;
-            break;
-        case SEGMENT_CORNER_TL:
-            p0 = outer_p0;
-            p1 = inner_p0;
-            break;
-        case SEGMENT_CORNER_BL:
-            p0 = vec2(outer_p0.x, outer_p1.y);
-            p1 = vec2(inner_p0.x, inner_p1.y);
-            break;
-        case SEGMENT_CORNER_TR:
-            p0 = vec2(outer_p1.x, outer_p1.y);
-            p1 = vec2(inner_p1.x, inner_p1.y);
-            break;
-        case SEGMENT_CORNER_BR:
-            p0 = vec2(outer_p1.x, outer_p0.y);
-            p1 = vec2(inner_p1.x, inner_p0.y);
-            break;
-    }
-
-    vec2 actual_pos = mix(p0, p1, aPosition.xy);
+                                      ClipArea area) {
+    vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
 
     vec4 layer_pos = get_layer_pos(actual_pos / uDevicePixelRatio, layer);
 
     // compute the point position in side the layer, in CSS space
     vec2 vertex_pos = actual_pos +
                       area.common_data.task_rect.p0 -
                       area.screen_origin;
 
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -26,18 +26,17 @@ void main(void) {
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Layer layer = fetch_layer(cmi.layer_address, cmi.layer_address);
     ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
     RectWithSize local_rect = mask.local_rect;
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
-                                               area,
-                                               cmi.segment);
+                                               area);
 
     vPos = vi.local_pos;
     vLayer = res.layer;
 
     vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.xy, res.uv_rect.zw - res.uv_rect.xy) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -59,18 +59,17 @@ void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Layer layer = fetch_layer(cmi.layer_address, cmi.layer_address);
     ClipData clip = fetch_clip(cmi.clip_data_address);
     RectWithSize local_rect = clip.rect.rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
-                                               area,
-                                               cmi.segment);
+                                               area);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
 
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
 
     vec2 r_tl = clip.top_left.outer_inner_radius.xy;
     vec2 r_tr = clip.top_right.outer_inner_radius.xy;
--- a/gfx/webrender/res/ellipse.glsl
+++ b/gfx/webrender/res/ellipse.glsl
@@ -50,39 +50,48 @@ float sdEllipse( vec2 p, in vec2 ab ) {
 
     float si = sqrt( 1.0 - co*co );
 
     vec2 r = vec2( ab.x*co, ab.y*si );
 
     return length(r - p ) * sign(p.y-r.y);
 }
 
-float distance_to_ellipse(vec2 p, vec2 radii) {
+float distance_to_ellipse(vec2 p, vec2 radii, float aa_range) {
     // sdEllipse fails on exact circles, so handle equal
     // radii here. The branch coherency should make this
     // a performance win for the circle case too.
+    float len = length(p);
     if (radii.x == radii.y) {
-        return length(p) - radii.x;
+        return len - radii.x;
     } else {
+        if (len < min(radii.x, radii.y) - aa_range) {
+          return -aa_range;
+        } else if (len > max(radii.x, radii.y) + aa_range) {
+          return aa_range;
+        }
+
         return sdEllipse(p, radii);
     }
 }
 
 float clip_against_ellipse_if_needed(
     vec2 pos,
     float current_distance,
     vec4 ellipse_center_radius,
-    vec2 sign_modifier
+    vec2 sign_modifier,
+    float aa_range
 ) {
     if (!all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy))) {
       return current_distance;
     }
 
     return distance_to_ellipse(pos - ellipse_center_radius.xy,
-                               ellipse_center_radius.zw);
+                               ellipse_center_radius.zw,
+                               aa_range);
 }
 
 float rounded_rect(vec2 pos,
                    vec4 clip_center_radius_tl,
                    vec4 clip_center_radius_tr,
                    vec4 clip_center_radius_br,
                    vec4 clip_center_radius_bl,
                    float aa_range) {
@@ -90,31 +99,35 @@ float rounded_rect(vec2 pos,
     // in a corner. If the fragment is in a corner, one of the clip_against_ellipse_if_needed
     // calls below will update it.
     float current_distance = -1.0;
 
     // Clip against each ellipse.
     current_distance = clip_against_ellipse_if_needed(pos,
                                                       current_distance,
                                                       clip_center_radius_tl,
-                                                      vec2(1.0));
+                                                      vec2(1.0),
+                                                      aa_range);
 
     current_distance = clip_against_ellipse_if_needed(pos,
                                                       current_distance,
                                                       clip_center_radius_tr,
-                                                      vec2(-1.0, 1.0));
+                                                      vec2(-1.0, 1.0),
+                                                      aa_range);
 
     current_distance = clip_against_ellipse_if_needed(pos,
                                                       current_distance,
                                                       clip_center_radius_br,
-                                                      vec2(-1.0));
+                                                      vec2(-1.0),
+                                                      aa_range);
 
     current_distance = clip_against_ellipse_if_needed(pos,
                                                       current_distance,
                                                       clip_center_radius_bl,
-                                                      vec2(1.0, -1.0));
+                                                      vec2(1.0, -1.0),
+                                                      aa_range);
 
     // Apply AA
     // See comment in ps_border_corner about the choice of constants.
 
     return distance_aa(aa_range, current_distance);
 }
 #endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -64,17 +64,17 @@ vec4[2] fetch_from_resource_cache_2(int 
     return vec4[2](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_LAYER              11
+#define VECS_PER_LAYER              7
 #define VECS_PER_RENDER_TASK        3
 #define VECS_PER_PRIM_HEADER        2
 #define VECS_PER_TEXT_RUN           3
 #define VECS_PER_GRADIENT           3
 #define VECS_PER_GRADIENT_STOP      2
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
@@ -138,71 +138,62 @@ vec4 fetch_from_resource_cache_1_direct(
 
 vec4 fetch_from_resource_cache_1(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return texelFetch(sResourceCache, uv, 0);
 }
 
 struct ClipScrollNode {
     mat4 transform;
-    mat4 inv_transform;
     vec4 local_clip_rect;
     vec2 reference_frame_relative_scroll_offset;
     vec2 scroll_offset;
     bool is_axis_aligned;
 };
 
 ClipScrollNode fetch_clip_scroll_node(int index) {
     ClipScrollNode node;
 
     // Create a UV base coord for each 8 texels.
     // This is required because trying to use an offset
     // of more than 8 texels doesn't work on some versions
     // of OSX.
     ivec2 uv = get_fetch_uv(index, VECS_PER_LAYER);
     ivec2 uv0 = ivec2(uv.x + 0, uv.y);
-    ivec2 uv1 = ivec2(uv.x + 8, uv.y);
 
     node.transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(0, 0));
     node.transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(1, 0));
     node.transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(2, 0));
     node.transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(3, 0));
 
-    node.inv_transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
-    node.inv_transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
-    node.inv_transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
-    node.inv_transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(7, 0));
-
-    vec4 clip_rect = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
+    vec4 clip_rect = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
     node.local_clip_rect = clip_rect;
 
-    vec4 offsets = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(1, 0));
+    vec4 offsets = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
     node.reference_frame_relative_scroll_offset = offsets.xy;
     node.scroll_offset = offsets.zw;
 
-    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(2, 0));
+    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
     node.is_axis_aligned = misc.x == 0.0;
 
     return node;
 }
 
 struct Layer {
     mat4 transform;
-    mat4 inv_transform;
     RectWithSize local_clip_rect;
     bool is_axis_aligned;
 };
 
 Layer fetch_layer(int clip_node_id, int scroll_node_id) {
     ClipScrollNode clip_node = fetch_clip_scroll_node(clip_node_id);
     ClipScrollNode scroll_node = fetch_clip_scroll_node(scroll_node_id);
 
     Layer layer;
     layer.transform = scroll_node.transform;
-    layer.inv_transform = scroll_node.inv_transform;
 
     vec4 local_clip_rect = clip_node.local_clip_rect;
     local_clip_rect.xy += clip_node.reference_frame_relative_scroll_offset;
     local_clip_rect.xy -= scroll_node.reference_frame_relative_scroll_offset;
     local_clip_rect.xy -= scroll_node.scroll_offset;
 
     layer.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
     layer.is_axis_aligned = scroll_node.is_axis_aligned;
@@ -309,35 +300,32 @@ BlurTask fetch_blur_task(int address) {
     );
 
     return task;
 }
 
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
-    vec4 inner_rect;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
     if (index == 0x7FFFFFFF) { //special sentinel task index
         area.common_data = RenderTaskCommonData(
             RectWithSize(vec2(0.0), vec2(0.0)),
             0.0
         );
         area.screen_origin = vec2(0.0);
-        area.inner_rect = vec4(0.0);
     } else {
         RenderTaskData task_data = fetch_render_task_data(index);
 
         area.common_data = task_data.common_data;
         area.screen_origin = task_data.data1.xy;
-        area.inner_rect = task_data.data2;
     }
 
     return area;
 }
 
 struct Gradient {
     vec4 start_end_point;
     vec4 tile_size_repeat;
@@ -543,19 +531,21 @@ vec4 untransform(vec2 ref, vec3 n, vec3 
     return r;
 }
 
 // Given a CSS space position, transform it back into the layer space.
 vec4 get_layer_pos(vec2 pos, Layer layer) {
     // get a point on the layer plane
     vec4 ah = layer.transform * vec4(0.0, 0.0, 0.0, 1.0);
     vec3 a = ah.xyz / ah.w;
+
     // get the normal to the layer plane
-    vec3 n = transpose(mat3(layer.inv_transform)) * vec3(0.0, 0.0, 1.0);
-    return untransform(pos, n, a, layer.inv_transform);
+    mat4 inv_transform = inverse(layer.transform);
+    vec3 n = transpose(mat3(inv_transform)) * vec3(0.0, 0.0, 1.0);
+    return untransform(pos, n, a, inv_transform);
 }
 
 // Compute a snapping offset in world space (adjusted to pixel ratio),
 // given local position on the layer and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
                          Layer layer,
                          RectWithSize snap_rect) {
     // Ensure that the snap rect is at *least* one device pixel in size.
@@ -734,27 +724,16 @@ ImageResource fetch_image_resource(int a
     return ImageResource(data[0], data[1].x);
 }
 
 ImageResource fetch_image_resource_direct(ivec2 address) {
     vec4 data[2] = fetch_from_resource_cache_2_direct(address);
     return ImageResource(data[0], data[1].x);
 }
 
-struct Rectangle {
-    vec4 color;
-    vec4 edge_aa_segment_mask;
-};
-
-Rectangle fetch_rectangle(int address) {
-    vec4 data[2] = fetch_from_resource_cache_2(address);
-    vec4 mask = vec4((int(data[1].x) & ivec4(1,2,4,8)) != ivec4(0));
-    return Rectangle(data[0], mask);
-}
-
 struct TextRun {
     vec4 color;
     vec4 bg_color;
     vec2 offset;
     int subpx_dir;
 };
 
 TextRun fetch_text_run(int address) {
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -346,20 +346,20 @@ void main(void) {
         // This error is corrected by offsetting the distance by half a device pixel.
         // This not entirely correct: it leaves an error that varries between
         // 0 and (sqrt(2) - 1)/2 = 0.2 pixels but it is hardly noticeable and is better
         // than the constant sqrt(2)/2 px error without the correction.
         // To correct this exactly we would need to offset p by half a pixel in the
         // direction of the center of the ellipse (a different offset for each corner).
 
         // Get signed distance from the inner/outer clips.
-        float d0 = distance_to_ellipse(p, vRadii0.xy);
-        float d1 = distance_to_ellipse(p, vRadii0.zw);
-        float d2 = distance_to_ellipse(p, vRadii1.xy);
-        float d3 = distance_to_ellipse(p, vRadii1.zw);
+        float d0 = distance_to_ellipse(p, vRadii0.xy, aa_range);
+        float d1 = distance_to_ellipse(p, vRadii0.zw, aa_range);
+        float d2 = distance_to_ellipse(p, vRadii1.xy, aa_range);
+        float d3 = distance_to_ellipse(p, vRadii1.zw, aa_range);
 
         // SDF subtract main radii
         float d_main = max(d0, -d1);
 
         // SDF subtract inner radii (double style borders)
         float d_inner = max(d2, -d3);
 
         // Select how to combine the SDF based on border style.
deleted file mode 100644
--- a/gfx/webrender/res/ps_rectangle.glsl
+++ /dev/null
@@ -1,54 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared
-
-varying vec4 vColor;
-
-#ifdef WR_FEATURE_TRANSFORM
-varying vec2 vLocalPos;
-#endif
-
-#ifdef WR_VERTEX_SHADER
-void main(void) {
-    Primitive prim = load_primitive();
-    Rectangle rect = fetch_rectangle(prim.specific_prim_address);
-    vColor = rect.color;
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex(prim.local_rect,
-                                           prim.local_rect,
-                                           prim.local_clip_rect,
-                                           rect.edge_aa_segment_mask,
-                                           prim.z,
-                                           prim.layer,
-                                           prim.task);
-    vLocalPos = vi.local_pos;
-#else
-    VertexInfo vi = write_vertex(prim.local_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-#ifdef WR_FEATURE_CLIP
-    write_clip(vi.screen_pos, prim.clip_area);
-#endif
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = init_transform_fs(vLocalPos);
-#endif
-
-#ifdef WR_FEATURE_CLIP
-    alpha *= do_clip();
-#endif
-    oFragColor = vColor * alpha;
-}
-#endif
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -4,17 +4,17 @@
 
 use api::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ColorF};
 use api::{LayerPoint, LayerRect};
 use api::{LayerPrimitiveInfo, LayerSize, NormalBorder, RepeatMode};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use frame_builder::FrameBuilder;
 use gpu_cache::GpuDataRequest;
-use internal_types::EdgeAaSegmentMask;
+use prim_store::{BrushAntiAliasMode, BrushSegmentDescriptor, BrushSegmentKind};
 use prim_store::{BorderPrimitiveCpu, PrimitiveContainer, TexelRect};
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
     None,
     Single, // Single instance needed - corner styles are same or similar.
@@ -369,66 +369,94 @@ impl FrameBuilder {
         });
         let all_edges_simple = edges.iter().all(|e| {
             *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
         });
 
         let has_no_curve = radius.is_zero();
 
         if has_no_curve && all_corners_simple && all_edges_simple {
-            let p0 = info.rect.origin;
-            let p1 = info.rect.bottom_right();
-            let rect_width = info.rect.size.width;
-            let rect_height = info.rect.size.height;
-            let mut info = info.clone();
+            let inner_rect = LayerRect::new(
+                LayerPoint::new(
+                    info.rect.origin.x + left_len,
+                    info.rect.origin.y + top_len,
+                ),
+                LayerSize::new(
+                    info.rect.size.width - left_len - right_len,
+                    info.rect.size.height - top_len - bottom_len,
+                ),
+            );
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
-                info.rect = LayerRect::new(p0, LayerSize::new(rect_width, top_len));
+                let descriptor = BrushSegmentDescriptor::new(
+                    &info.rect,
+                    &inner_rect,
+                    Some(&[
+                        BrushSegmentKind::TopLeft,
+                        BrushSegmentKind::TopMid,
+                        BrushSegmentKind::TopRight
+                    ]),
+                );
                 self.add_solid_rectangle(
                     clip_and_scroll,
                     &info,
                     border.top.color,
-                    EdgeAaSegmentMask::BOTTOM,
+                    Some(Box::new(descriptor)),
+                    BrushAntiAliasMode::Segment,
                 );
             }
             if left_edge == BorderEdgeKind::Solid {
-                info.rect = LayerRect::new(
-                    LayerPoint::new(p0.x, p0.y + top_len),
-                    LayerSize::new(left_len, rect_height - top_len - bottom_len),
+                let descriptor = BrushSegmentDescriptor::new(
+                    &info.rect,
+                    &inner_rect,
+                    Some(&[
+                        BrushSegmentKind::MidLeft,
+                    ]),
                 );
                 self.add_solid_rectangle(
                     clip_and_scroll,
                     &info,
                     border.left.color,
-                    EdgeAaSegmentMask::RIGHT,
+                    Some(Box::new(descriptor)),
+                    BrushAntiAliasMode::Segment,
                 );
             }
             if right_edge == BorderEdgeKind::Solid {
-                info.rect = LayerRect::new(
-                    LayerPoint::new(p1.x - right_len, p0.y + top_len),
-                    LayerSize::new(right_len, rect_height - top_len - bottom_len),
+                let descriptor = BrushSegmentDescriptor::new(
+                    &info.rect,
+                    &inner_rect,
+                    Some(&[
+                        BrushSegmentKind::MidRight,
+                    ]),
                 );
                 self.add_solid_rectangle(
                     clip_and_scroll,
                     &info,
                     border.right.color,
-                    EdgeAaSegmentMask::LEFT,
+                    Some(Box::new(descriptor)),
+                    BrushAntiAliasMode::Segment,
                 );
             }
             if bottom_edge == BorderEdgeKind::Solid {
-                info.rect = LayerRect::new(
-                    LayerPoint::new(p0.x, p1.y - bottom_len),
-                    LayerSize::new(rect_width, bottom_len),
+                let descriptor = BrushSegmentDescriptor::new(
+                    &info.rect,
+                    &inner_rect,
+                    Some(&[
+                        BrushSegmentKind::BottomLeft,
+                        BrushSegmentKind::BottomMid,
+                        BrushSegmentKind::BottomRight
+                    ]),
                 );
                 self.add_solid_rectangle(
                     clip_and_scroll,
                     &info,
                     border.bottom.color,
-                    EdgeAaSegmentMask::TOP,
+                    Some(Box::new(descriptor)),
+                    BrushAntiAliasMode::Segment,
                 );
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
             let mut corner_instances = [BorderCornerInstance::Single; 4];
 
             for (i, corner) in corners.iter().enumerate() {
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -4,18 +4,17 @@
 
 use api::{BorderRadiusKind, ColorF, LayerPoint, LayerRect, LayerSize, LayerVector2D};
 use api::{BorderRadius, BoxShadowClipMode, LayoutSize, LayerPrimitiveInfo};
 use api::{ClipMode, ClipAndScrollInfo, ComplexClipRegion, LocalClip};
 use api::{PipelineId};
 use app_units::Au;
 use clip::ClipSource;
 use frame_builder::FrameBuilder;
-use internal_types::EdgeAaSegmentMask;
-use prim_store::{PrimitiveContainer, RectangleContent, RectanglePrimitive};
+use prim_store::{BrushAntiAliasMode, PrimitiveContainer};
 use prim_store::{BrushMaskKind, BrushKind, BrushPrimitive};
 use picture::PicturePrimitive;
 use util::RectHelpers;
 use render_task::MAX_BLUR_STD_DEVIATION;
 
 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
 pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 
@@ -126,20 +125,24 @@ impl FrameBuilder {
                     )
                 }
             };
 
             self.add_primitive(
                 clip_and_scroll,
                 &fast_info,
                 clips,
-                PrimitiveContainer::Rectangle(RectanglePrimitive {
-                    content: RectangleContent::Fill(*color),
-                    edge_aa_segment_mask: EdgeAaSegmentMask::empty(),
-                }),
+                PrimitiveContainer::Brush(
+                    BrushPrimitive::new(BrushKind::Solid {
+                            color: *color,
+                        },
+                        None,
+                        BrushAntiAliasMode::Primitive,
+                    )
+                ),
             );
         } else {
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
             let cache_key = BoxShadowCacheKey {
                 width: Au::from_f32_px(shadow_rect.size.width),
                 height: Au::from_f32_px(shadow_rect.size.height),
@@ -173,22 +176,24 @@ impl FrameBuilder {
                     // If the outset box shadow has a uniform corner side, we can
                     // just blur the top left corner, and stretch / mirror that
                     // across the primitive.
                     if let Some(corner_size) = corner_size {
                         radii_kind = BorderRadiusKind::Uniform;
                         width = MASK_CORNER_PADDING + corner_size.width.max(BLUR_SAMPLE_SCALE * blur_radius);
                         height = MASK_CORNER_PADDING + corner_size.height.max(BLUR_SAMPLE_SCALE * blur_radius);
 
-                        brush_prim = BrushPrimitive {
-                            kind: BrushKind::Mask {
+                        brush_prim = BrushPrimitive::new(
+                            BrushKind::Mask {
                                 clip_mode: brush_clip_mode,
                                 kind: BrushMaskKind::Corner(corner_size),
-                            }
-                        };
+                            },
+                            None,
+                            BrushAntiAliasMode::Primitive,
+                        );
                     } else {
                         // Create a minimal size primitive mask to blur. In this
                         // case, we ensure the size of each corner is the same,
                         // to simplify the shader logic that stretches the blurred
                         // result across the primitive.
                         radii_kind = BorderRadiusKind::NonUniform;
                         let max_width = shadow_radius.top_left.width
                                             .max(shadow_radius.bottom_left.width)
@@ -200,22 +205,24 @@ impl FrameBuilder {
                                             .max(shadow_radius.bottom_right.height);
 
                         width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
                         height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
 
                         let clip_rect = LayerRect::new(LayerPoint::zero(),
                                                        LayerSize::new(width, height));
 
-                        brush_prim = BrushPrimitive {
-                            kind: BrushKind::Mask {
+                        brush_prim = BrushPrimitive::new(
+                            BrushKind::Mask {
                                 clip_mode: brush_clip_mode,
                                 kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
-                            }
-                        };
+                            },
+                            None,
+                            BrushAntiAliasMode::Primitive,
+                        );
                     };
 
                     // Construct a mask primitive to add to the picture.
                     let brush_rect = LayerRect::new(LayerPoint::zero(),
                                                     LayerSize::new(width, height));
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
                         &brush_info,
@@ -283,22 +290,24 @@ impl FrameBuilder {
                     let mut adjusted_blur_std_deviation = blur_radius * 0.5;
                     let mut inflate_size = 1.0;
                     while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
                         adjusted_blur_std_deviation *= 0.5;
                         inflate_size += 1.0;
                     }
 
                     let brush_rect = brush_rect.inflate(inflate_size, inflate_size);
-                    let brush_prim = BrushPrimitive {
-                        kind: BrushKind::Mask {
+                    let brush_prim = BrushPrimitive::new(
+                        BrushKind::Mask {
                             clip_mode: brush_clip_mode,
                             kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
-                        }
-                    };
+                        },
+                        None,
+                        BrushAntiAliasMode::Primitive,
+                    );
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
                         &brush_info,
                         Vec::new(),
                         PrimitiveContainer::Brush(brush_prim),
                     );
 
                     // Create a box shadow picture primitive and add
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,22 +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 api::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, ImageRendering, LayerPoint};
-use api::{ClipMode, LayerRect};
-use api::{LayerToWorldTransform, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, ImageMask, ImageRendering};
+use api::{LayerPoint, LayerRect, LayerToWorldTransform, LayoutPoint, LayoutVector2D, LocalClip};
 use border::BorderCornerClipSource;
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::ResourceCache;
-use util::{extract_inner_rect_safe, MaxRect, TransformedRect};
+use util::{MaxRect, calculate_screen_bounding_rect, extract_inner_rect_safe};
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub main: LayerRect,
@@ -248,22 +247,22 @@ impl ClipSources {
     }
 
     pub fn get_screen_bounds(
         &self,
         transform: &LayerToWorldTransform,
         device_pixel_ratio: f32,
     ) -> (DeviceIntRect, Option<DeviceIntRect>) {
         let screen_inner_rect =
-            TransformedRect::new(&self.local_inner_rect, transform, device_pixel_ratio);
+            calculate_screen_bounding_rect(transform, &self.local_inner_rect, device_pixel_ratio);
         let screen_outer_rect = self.local_outer_rect.map(|outer_rect|
-            TransformedRect::new(&outer_rect, transform, device_pixel_ratio).bounding_rect
+            calculate_screen_bounding_rect(transform, &outer_rect, device_pixel_ratio)
         );
 
-        (screen_inner_rect.bounding_rect, screen_outer_rect)
+        (screen_inner_rect, screen_outer_rect)
     }
 }
 
 /// Represents a local rect and a device space
 /// rectangles that are either outside or inside bounds.
 #[derive(Clone, Debug, PartialEq)]
 pub struct Geometry {
     pub local_rect: LayerRect,
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -65,57 +65,55 @@ pub enum NodeType {
 
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
 }
 
+impl NodeType {
+    fn is_reference_frame(&self) -> bool {
+        match *self {
+            NodeType::ReferenceFrame(_) => true,
+            _ => false,
+        }
+    }
+}
+
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Debug)]
 pub struct ClipScrollNode {
     /// Viewing rectangle in the coordinate system of the parent reference frame.
     pub local_viewport_rect: LayerRect,
 
     /// Clip rect of this node - typically the same as viewport rect, except
     /// in overscroll cases.
     pub local_clip_rect: LayerRect,
 
-    /// Viewport rectangle clipped against parent layer(s) viewport rectangles.
-    /// This is in the coordinate system of the node origin.
-    /// Precisely, it combines the local clipping rectangles of all the parent
-    /// nodes on the way to the root, including those of `ClipRegion` rectangles.
-    /// The combined clip is reset to maximum when an incompatible coordinate
-    /// system is encountered.
-    pub combined_local_viewport_rect: LayerRect,
-
-    /// World transform for the viewport rect itself. This is the parent
-    /// reference frame transformation plus the scrolling offsets provided by
-    /// the nodes in between the reference frame and this node.
+    /// The transformation for this viewport in world coordinates is the transformation for
+    /// our parent reference frame, plus any accumulated scrolling offsets from nodes
+    /// between our reference frame and this node. For reference frames, we also include
+    /// whatever local transformation this reference frame provides. This can be combined
+    /// with the local_viewport_rect to get its position in world space.
     pub world_viewport_transform: LayerToWorldTransform,
 
     /// World transform for content transformed by this node.
     pub world_content_transform: LayerToWorldTransform,
 
-    /// The scroll offset of all the nodes between us and our parent reference frame.
-    /// This is used to calculate intersections between us and content or nodes that
-    /// are also direct children of our reference frame.
-    pub reference_frame_relative_scroll_offset: LayerVector2D,
-
     /// Pipeline that this layer belongs to
     pub pipeline_id: PipelineId,
 
     /// Parent layer. If this is None, we are the root node.
     pub parent: Option<ClipId>,
 
     /// Child layers
     pub children: Vec<ClipId>,
 
-    /// Whether or not this node is a reference frame.
+    /// The type of this node and any data associated with that node type.
     pub node_type: NodeType,
 
     /// The node in the chain of clips that are necessary to clip display items
     /// that have this ClipScrollNode as their clip parent. This will be used to
     /// generate clip tasks.
     pub clip_chain_node: ClipChain,
 
     /// The intersected outer bounds of the clips for this node.
@@ -134,20 +132,18 @@ impl ClipScrollNode {
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
         rect: &LayerRect,
         node_type: NodeType
     ) -> Self {
         ClipScrollNode {
             local_viewport_rect: *rect,
             local_clip_rect: *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: parent_id,
             children: Vec::new(),
             pipeline_id,
             node_type: node_type,
             clip_chain_node: None,
             combined_clip_outer_bounds: DeviceIntRect::max_rect(),
             coordinate_system_id: CoordinateSystemId(0),
             node_data_index: ClipScrollNodeIndex(0),
@@ -263,73 +259,119 @@ impl ClipScrollNode {
         }
 
         scrolling.offset = new_offset;
         scrolling.bouncing_back = false;
         scrolling.started_bouncing_back = false;
         true
     }
 
+    pub fn update_to_empty_rect(&mut self) {
+        self.combined_clip_outer_bounds = DeviceIntRect::zero();
+        self.world_content_transform = LayerToWorldTransform::identity();
+        self.world_viewport_transform = LayerToWorldTransform::identity();
+        self.clip_chain_node = None;
+    }
+
+    pub fn push_gpu_node_data(
+        &mut self,
+        state: &TransformUpdateState,
+        node_data: &mut Vec<ClipScrollNodeData>
+    ) {
+        if self.combined_clip_outer_bounds.is_empty() {
+            node_data.push(ClipScrollNodeData::invalid());
+            return;
+        }
+
+        let local_clip_rect = match self.node_type {
+            _ if self.world_content_transform.has_perspective_component() => LayerRect::max_rect(),
+            NodeType::ReferenceFrame(ref info) => {
+                info.resolved_transform.with_destination::<LayerPixel>()
+                    .inverse_rect_footprint(&state.parent_combined_viewport_rect)
+            }
+            NodeType::Clip(_) | NodeType::ScrollFrame(_) => {
+                state.parent_combined_viewport_rect
+                    .intersection(&self.local_clip_rect)
+                    .unwrap_or(LayerRect::zero())
+            }
+            NodeType::StickyFrame(ref sticky_info) => {
+                state.parent_combined_viewport_rect
+                    .translate(&-sticky_info.current_offset)
+                    .intersection(&self.local_clip_rect)
+                    .unwrap_or(LayerRect::zero())
+            }
+        };
+
+        let transform_kind = if self.world_content_transform.preserves_2d_axis_alignment() {
+            TransformedRectKind::AxisAligned
+        } else {
+            TransformedRectKind::Complex
+        };
+
+        let reference_frame_relative_scroll_offset = match self.node_type {
+            NodeType::ReferenceFrame(_) => LayerVector2D::zero(),
+            NodeType::Clip(_) | NodeType::ScrollFrame(_) => state.parent_accumulated_scroll_offset,
+            NodeType::StickyFrame(ref sticky_info) =>
+                    state.parent_accumulated_scroll_offset + sticky_info.current_offset,
+        };
+
+        let data = ClipScrollNodeData {
+            transform: self.world_content_transform,
+            local_clip_rect,
+            reference_frame_relative_scroll_offset,
+            scroll_offset: self.scroll_offset(),
+            transform_kind: transform_kind as u32 as f32,
+            padding: [0.0; 3],
+        };
+
+        // Write the data that will be made available to the GPU for this node.
+        node_data.push(data);
+    }
+
     pub fn update(
         &mut self,
         state: &mut TransformUpdateState,
-        node_data: &mut Vec<ClipScrollNodeData>,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         scene_properties: &SceneProperties,
     ) {
-        // We set this earlier so that we can use it before we have all the data necessary
-        // to populate the ClipScrollNodeData.
-        self.node_data_index = ClipScrollNodeIndex(node_data.len() as u32);
+        // If any of our parents was not rendered, we are not rendered either and can just
+        // quit here.
+        if state.combined_outer_clip_bounds.is_empty() {
+            self.update_to_empty_rect();
+            return;
+        }
+
+        // If this node is a reference frame, we check if the determinant is 0, which means it
+        // has a non-invertible matrix. For non-reference-frames we assume that they will
+        // produce only additional translations which should be invertible.
+        if self.node_type.is_reference_frame() {
+            if self.world_content_transform.determinant() == 0.0 {
+                self.update_to_empty_rect();
+                return;
+            }
+        }
 
         self.update_transform(state, scene_properties);
         self.update_clip_work_item(
             state,
             device_pixel_ratio,
             clip_store,
             resource_cache,
             gpu_cache,
         );
 
-        let local_clip_rect = if self.world_content_transform.has_perspective_component() {
-            LayerRect::max_rect()
-        } else {
-            self.combined_local_viewport_rect
-        };
-
-        let data = match self.world_content_transform.inverse() {
-            Some(inverse) => {
-                let transform_kind = if self.world_content_transform.preserves_2d_axis_alignment() {
-                    TransformedRectKind::AxisAligned
-                } else {
-                    TransformedRectKind::Complex
-                };
+        // This indicates that we are entirely clipped out.
+        if state.combined_outer_clip_bounds.is_empty() {
+            self.update_to_empty_rect();
+            return;
+        }
 
-                ClipScrollNodeData {
-                    transform: self.world_content_transform,
-                    inv_transform: inverse,
-                    local_clip_rect,
-                    reference_frame_relative_scroll_offset:
-                        self.reference_frame_relative_scroll_offset,
-                    scroll_offset: self.scroll_offset(),
-                    transform_kind: transform_kind as u32 as f32,
-                    padding: [0.0; 3],
-                }
-            }
-            None => {
-                state.combined_outer_clip_bounds = DeviceIntRect::zero();
-                self.combined_clip_outer_bounds = DeviceIntRect::zero();
-                ClipScrollNodeData::invalid()
-            }
-        };
-
-        // Write the data that will be made available to the GPU for this node.
-        node_data.push(data);
     }
 
     pub fn update_clip_work_item(
         &mut self,
         state: &mut TransformUpdateState,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
@@ -396,123 +438,90 @@ impl ClipScrollNode {
         state.parent_clip_chain = self.clip_chain_node.clone();
     }
 
     pub fn update_transform(
         &mut self,
         state: &mut TransformUpdateState,
         scene_properties: &SceneProperties,
     ) {
+        if self.node_type.is_reference_frame() {
+            self.update_transform_for_reference_frame(state, scene_properties);
+            return;
+        }
+
         // We calculate this here to avoid a double-borrow later.
         let sticky_offset = self.calculate_sticky_offset(
             &state.nearest_scrolling_ancestor_offset,
             &state.nearest_scrolling_ancestor_viewport,
         );
 
-        let (local_transform, accumulated_scroll_offset) = match self.node_type {
-            NodeType::ReferenceFrame(ref mut info) => {
-                // Resolve the transform against any property bindings.
-                let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
-                info.resolved_transform = LayerToScrollTransform::create_translation(
-                    info.origin_in_parent_reference_frame.x,
-                    info.origin_in_parent_reference_frame.y,
-                    0.0
-                ).pre_mul(&source_transform)
-                 .pre_mul(&info.source_perspective);
+        // The transformation for the bounds of our viewport is the parent reference frame
+        // transform, plus any accumulated scroll offset from our parents, plus any offset
+        // provided by our own sticky positioning.
+        let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset;
+        self.world_viewport_transform = if accumulated_offset != LayerVector2D::zero() {
+            state.parent_reference_frame_transform.pre_translate(accumulated_offset.to_3d())
+        } else {
+            state.parent_reference_frame_transform
+        };
+
+        // The transformation for any content inside of us is the viewport transformation, plus
+        // whatever scrolling offset we supply as well.
+        let scroll_offset = self.scroll_offset();
+        self.world_content_transform = if scroll_offset != LayerVector2D::zero() {
+            self.world_viewport_transform.pre_translate(scroll_offset.to_3d())
+        } else {
+            self.world_viewport_transform
+        };
+
+        match self.node_type {
+            NodeType::StickyFrame(ref mut info) => info.current_offset = sticky_offset,
+            _ => {},
+        }
 
-                self.combined_local_viewport_rect = info.resolved_transform
-                    .with_destination::<LayerPixel>()
-                    .inverse_rect_footprint(&state.parent_combined_viewport_rect);
-                self.reference_frame_relative_scroll_offset = LayerVector2D::zero();
-                (info.resolved_transform, state.parent_accumulated_scroll_offset)
-            }
-            NodeType::Clip(_) | NodeType::ScrollFrame(_) => {
-                // Move the parent's viewport into the local space (of the node origin)
-                // and intersect with the local clip rectangle to get the local viewport.
-                self.combined_local_viewport_rect =
-                    state.parent_combined_viewport_rect
-                    .intersection(&self.local_clip_rect)
-                    .unwrap_or(LayerRect::zero());
-                self.reference_frame_relative_scroll_offset =
-                    state.parent_accumulated_scroll_offset;
-                (
-                    LayerToScrollTransform::identity(),
-                    self.reference_frame_relative_scroll_offset,
-                )
-            }
-            NodeType::StickyFrame(ref mut info) => {
-                info.current_offset = sticky_offset;
-                self.combined_local_viewport_rect =
-                    state.parent_combined_viewport_rect
-                    .translate(&-sticky_offset)
-                    .intersection(&self.local_clip_rect)
-                    .unwrap_or(LayerRect::zero());
-                self.reference_frame_relative_scroll_offset =
-                    state.parent_accumulated_scroll_offset + sticky_offset;
-                (LayerToScrollTransform::identity(), self.reference_frame_relative_scroll_offset)
-            }
+        self.coordinate_system_id = state.current_coordinate_system_id;
+    }
+
+    pub fn update_transform_for_reference_frame(
+        &mut self,
+        state: &mut TransformUpdateState,
+        scene_properties: &SceneProperties,
+    ) {
+        let info = match self.node_type {
+            NodeType::ReferenceFrame(ref mut info) => info,
+            _ => unreachable!("Called update_transform_for_reference_frame on non-ReferenceFrame"),
         };
 
+        // Resolve the transform against any property bindings.
+        let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+        info.resolved_transform = LayerToScrollTransform::create_translation(
+            info.origin_in_parent_reference_frame.x,
+            info.origin_in_parent_reference_frame.y,
+            0.0
+        ).pre_mul(&source_transform)
+         .pre_mul(&info.source_perspective);
+
+        if !info.resolved_transform.preserves_2d_axis_alignment() ||
+           info.resolved_transform.has_perspective_component() {
+            state.current_coordinate_system_id = state.next_coordinate_system_id;
+            state.next_coordinate_system_id = state.next_coordinate_system_id.next();
+            self.coordinate_system_id = state.current_coordinate_system_id;
+        }
+
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
-        // between our reference frame and this node. For reference frames, we also include
+        // between our reference frame and this node. Finally, we also include
         // whatever local transformation this reference frame provides. This can be combined
         // with the local_viewport_rect to get its position in world space.
         self.world_viewport_transform = state
             .parent_reference_frame_transform
-            .pre_translate(accumulated_scroll_offset.to_3d())
-            .pre_mul(&local_transform.with_destination::<LayerPixel>());
-
-        // The transformation for any content inside of us is the viewport transformation, plus
-        // whatever scrolling offset we supply as well.
-        let scroll_offset = self.scroll_offset();
-        self.world_content_transform = self.world_viewport_transform
-            .pre_translate(scroll_offset.to_3d());
-
-        // The transformation we are passing is the transformation of the parent
-        // reference frame and the offset is the accumulated offset of all the nodes
-        // between us and the parent reference frame. If we are a reference frame,
-        // we need to reset both these values.
-        match self.node_type {
-            NodeType::ReferenceFrame(ref info) => {
-                state.parent_reference_frame_transform = self.world_viewport_transform;
-                state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
-                state.parent_accumulated_scroll_offset = LayerVector2D::zero();
-                state.nearest_scrolling_ancestor_viewport =
-                    state.nearest_scrolling_ancestor_viewport
-                       .translate(&info.origin_in_parent_reference_frame);
-
-                if !info.resolved_transform.preserves_2d_axis_alignment() {
-                    state.current_coordinate_system_id = state.next_coordinate_system_id;
-                    state.next_coordinate_system_id = state.next_coordinate_system_id.next();
-                }
-            },
-            NodeType::Clip(..) => {
-                state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
-            },
-            NodeType::ScrollFrame(ref scrolling) => {
-                state.parent_combined_viewport_rect =
-                        self.combined_local_viewport_rect.translate(&-scrolling.offset);
-                state.parent_accumulated_scroll_offset =
-                    scrolling.offset + state.parent_accumulated_scroll_offset;
-                state.nearest_scrolling_ancestor_offset = scrolling.offset;
-                state.nearest_scrolling_ancestor_viewport = self.local_viewport_rect;
-            }
-            NodeType::StickyFrame(ref info) => {
-                // We don't translate the combined rect by the sticky offset, because sticky
-                // offsets actually adjust the node position itself, whereas scroll offsets
-                // only apply to contents inside the node.
-                state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
-                state.parent_accumulated_scroll_offset =
-                    info.current_offset + state.parent_accumulated_scroll_offset;
-            }
-        }
-
-        // Store coord system ID, and also the ID used for shaders to reference this node.
-        self.coordinate_system_id = state.current_coordinate_system_id;
+            .pre_translate(state.parent_accumulated_scroll_offset.to_3d())
+            .pre_mul(&info.resolved_transform.with_destination::<LayerPixel>());
+        self.world_content_transform = self.world_viewport_transform;
     }
 
     fn calculate_sticky_offset(
         &self,
         viewport_scroll_offset: &LayerVector2D,
         viewport_rect: &LayerRect,
     ) -> LayerVector2D {
         let info = match self.node_type {
@@ -614,16 +623,66 @@ impl ClipScrollNode {
                                          &info.vertical_offset_bounds);
         sticky_offset.x = clamp_adjusted(sticky_offset.x,
                                          info.previously_applied_offset.x,
                                          &info.horizontal_offset_bounds);
 
         sticky_offset
     }
 
+    pub fn prepare_state_for_children(
+        &self,
+        state: &mut TransformUpdateState,
+        node_data: &Vec<ClipScrollNodeData>
+    ) {
+        if self.combined_clip_outer_bounds.is_empty() {
+            state.parent_combined_viewport_rect = LayerRect::zero();
+            state.combined_outer_clip_bounds = DeviceIntRect::zero();
+            state.parent_clip_chain = None;
+            return;
+        }
+
+        let combined_local_viewport_rect =
+            node_data[self.node_data_index.0 as usize].local_clip_rect;
+
+        // The transformation we are passing is the transformation of the parent
+        // reference frame and the offset is the accumulated offset of all the nodes
+        // between us and the parent reference frame. If we are a reference frame,
+        // we need to reset both these values.
+        match self.node_type {
+            NodeType::ReferenceFrame(ref info) => {
+                state.parent_reference_frame_transform = self.world_viewport_transform;
+                state.parent_combined_viewport_rect = combined_local_viewport_rect;
+                state.parent_accumulated_scroll_offset = LayerVector2D::zero();
+                state.nearest_scrolling_ancestor_viewport =
+                    state.nearest_scrolling_ancestor_viewport
+                       .translate(&info.origin_in_parent_reference_frame);
+            }
+            NodeType::Clip(..) => {
+                state.parent_combined_viewport_rect = combined_local_viewport_rect;
+            },
+            NodeType::ScrollFrame(ref scrolling) => {
+                state.parent_combined_viewport_rect =
+                        combined_local_viewport_rect.translate(&-scrolling.offset);
+                state.parent_accumulated_scroll_offset =
+                    scrolling.offset + state.parent_accumulated_scroll_offset;
+                state.nearest_scrolling_ancestor_offset = scrolling.offset;
+                state.nearest_scrolling_ancestor_viewport = self.local_viewport_rect;
+            }
+            NodeType::StickyFrame(ref info) => {
+                // We don't translate the combined rect by the sticky offset, because sticky
+                // offsets actually adjust the node position itself, whereas scroll offsets
+                // only apply to contents inside the node.
+                state.parent_combined_viewport_rect = combined_local_viewport_rect;
+                state.parent_accumulated_scroll_offset =
+                    info.current_offset + state.parent_accumulated_scroll_offset;
+            }
+        }
+    }
+
     pub fn scrollable_size(&self) -> LayerSize {
         match self.node_type {
            NodeType:: ScrollFrame(state) => state.scrollable_size,
             _ => LayerSize::zero(),
         }
     }
 
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPoint, LayerRect};
 use api::{LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase};
 use api::{PropertyBinding, LayoutTransform, ScrollLayerState, ScrollLocation, WorldPoint};
 use clip::ClipStore;
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState, StickyFrameInfo};
 use gpu_cache::GpuCache;
-use gpu_types::ClipScrollNodeData;
+use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use render_task::ClipChain;
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::MaxRect;
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
@@ -380,50 +380,58 @@ impl ClipScrollTree {
     fn update_node(
         &mut self,
         layer_id: ClipId,
         state: &mut TransformUpdateState,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        node_data: &mut Vec<ClipScrollNodeData>,
+        gpu_node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
             let node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
             };
 
+            // We set this early so that we can use it to populate the ClipChain.
+            node.node_data_index = ClipScrollNodeIndex(gpu_node_data.len() as u32);
+
             node.update(
                 &mut state,
-                node_data,
                 device_pixel_ratio,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 scene_properties,
             );
 
+            node.push_gpu_node_data(&state, gpu_node_data);
+
+            if !node.children.is_empty() {
+                node.prepare_state_for_children(&mut state, gpu_node_data);
+            }
+
             node.children.clone()
         };
 
         for child_layer_id in node_children {
             self.update_node(
                 child_layer_id,
                 &mut state,
                 device_pixel_ratio,
                 clip_store,
                 resource_cache,
                 gpu_cache,
-                node_data,
+                gpu_node_data,
                 scene_properties,
             );
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
@@ -547,20 +555,16 @@ impl ClipScrollTree {
         }
 
         pt.add_item(format!(
             "local_viewport_rect: {:?}",
             node.local_viewport_rect
         ));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!(
-            "combined_local_viewport_rect: {:?}",
-            node.combined_local_viewport_rect
-        ));
-        pt.add_item(format!(
             "world_viewport_transform: {:?}",
             node.world_viewport_transform
         ));
         pt.add_item(format!(
             "world_content_transform: {:?}",
             node.world_content_transform
         ));
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,22 +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 super::shader_source;
 use api::{ColorF, ImageFormat};
-use api::{DeviceIntRect, DeviceUintSize};
+use api::{DeviceIntRect, DeviceUintRect, DeviceUintSize};
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::{FastHashMap, RenderTargetInfo};
+use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::fs::File;
 use std::io::Read;
 use std::iter::repeat;
+use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
 use std::thread;
 
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
@@ -111,16 +113,25 @@ pub struct VertexDescriptor {
     pub instance_attributes: &'static [VertexAttribute],
 }
 
 enum FBOTarget {
     Read,
     Draw,
 }
 
+/// Method of uploading texel data from CPU to GPU.
+#[derive(Debug, Clone)]
+pub enum UploadMethod {
+    /// Just call `glTexSubImage` directly with the CPU data pointer
+    Immediate,
+    /// Accumulate the changes in PBO first before transferring to a texture.
+    PixelBuffer(VertexUsageHint),
+}
+
 pub fn get_gl_format_bgra(gl: &gl::Gl) -> gl::GLuint {
     match gl.get_type() {
         gl::GlType::Gl => GL_FORMAT_BGRA_GL,
         gl::GlType::Gles => GL_FORMAT_BGRA_GLES,
     }
 }
 
 fn get_shader_version(gl: &gl::Gl) -> &'static str {
@@ -577,23 +588,23 @@ pub enum ShaderError {
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [gl::GLuint; 16],
     bound_program: gl::GLuint,
     bound_vao: gl::GLuint,
-    bound_pbo: gl::GLuint,
     bound_read_fbo: FBOId,
     bound_draw_fbo: FBOId,
     default_read_fbo: gl::GLuint,
     default_draw_fbo: gl::GLuint,
 
-    pub device_pixel_ratio: f32,
+    device_pixel_ratio: f32,
+    upload_method: UploadMethod,
 
     // HW or API capabilties
     capabilities: Capabilities,
 
     // debug
     inside_frame: bool,
 
     // resources
@@ -607,38 +618,39 @@ pub struct Device {
     // frames and GPU frames.
     frame_id: FrameId,
 }
 
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
+        upload_method: UploadMethod,
         _file_changed_handler: Box<FileWatcherHandler>,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
         let max_texture_size = gl.get_integer_v(gl::MAX_TEXTURE_SIZE) as u32;
         let renderer_name = gl.get_string(gl::RENDERER);
 
         Device {
             gl,
             resource_override_path,
-            // This is initialized to 1 by default, but it is set
-            // every frame by the call to begin_frame().
+            // This is initialized to 1 by default, but it is reset
+            // at the beginning of each frame in `Renderer::bind_frame_data`.
             device_pixel_ratio: 1.0,
+            upload_method,
             inside_frame: false,
 
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
             bound_textures: [0; 16],
             bound_program: 0,
             bound_vao: 0,
-            bound_pbo: 0,
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
             max_texture_size,
             renderer_name,
             cached_programs,
@@ -649,32 +661,35 @@ impl Device {
     pub fn gl(&self) -> &gl::Gl {
         &*self.gl
     }
 
     pub fn rc_gl(&self) -> &Rc<gl::Gl> {
         &self.gl
     }
 
+    pub fn set_device_pixel_ratio(&mut self, ratio: f32) {
+        self.device_pixel_ratio = ratio;
+    }
+
     pub fn update_program_cache(&mut self, cached_programs: Rc<ProgramCache>) {
         self.cached_programs = Some(cached_programs);
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.max_texture_size
     }
 
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
     pub fn reset_state(&mut self) {
         self.bound_textures = [0; 16];
         self.bound_vao = 0;
-        self.bound_pbo = 0;
         self.bound_read_fbo = FBOId(0);
         self.bound_draw_fbo = FBOId(0);
     }
 
     pub fn compile_shader(
         gl: &gl::Gl,
         name: &str,
         shader_type: gl::GLenum,
@@ -722,17 +737,16 @@ impl Device {
         self.gl.bind_vertex_array(0);
 
         // FBO state
         self.bound_read_fbo = FBOId(self.default_read_fbo);
         self.bound_draw_fbo = FBOId(self.default_draw_fbo);
 
         // Pixel op state
         self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
-        self.bound_pbo = 0;
         self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
 
         // Default is sampler 0, always
         self.gl.active_texture(gl::TEXTURE0);
 
         self.frame_id
     }
 
@@ -1068,17 +1082,16 @@ impl Device {
             dest_rect.origin.y + dest_rect.size.height,
             gl::COLOR_BUFFER_BIT,
             gl::LINEAR,
         );
     }
 
     pub fn free_texture_storage(&mut self, texture: &mut Texture) {
         debug_assert!(self.inside_frame);
-        debug_assert_eq!(self.bound_pbo, 0);
 
         if texture.format == ImageFormat::Invalid {
             return;
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
 
         let (internal_format, gl_format) =
@@ -1317,121 +1330,55 @@ impl Device {
         PBO { id }
     }
 
     pub fn delete_pbo(&mut self, mut pbo: PBO) {
         self.gl.delete_buffers(&[pbo.id]);
         pbo.id = 0;
     }
 
-    pub fn bind_pbo(&mut self, pbo: Option<&PBO>) {
+    pub fn upload_texture<'a, T>(
+        &'a mut self,
+        texture: &'a Texture,
+        pbo: &PBO,
+        upload_count: usize,
+    ) -> TextureUploader<'a, T> {
         debug_assert!(self.inside_frame);
-        let pbo_id = pbo.map_or(0, |pbo| pbo.id);
-
-        if self.bound_pbo != pbo_id {
-            self.bound_pbo = pbo_id;
-
-            self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo_id);
-        }
-    }
-
-    pub fn update_pbo_data<T>(&mut self, data: &[T]) {
-        debug_assert!(self.inside_frame);
-        debug_assert_ne!(self.bound_pbo, 0);
-
-        gl::buffer_data(&*self.gl, gl::PIXEL_UNPACK_BUFFER, data, gl::STREAM_DRAW);
-    }
-
-    pub fn orphan_pbo(&mut self, new_size: usize) {
-        debug_assert!(self.inside_frame);
-        debug_assert_ne!(self.bound_pbo, 0);
+        self.bind_texture(DEFAULT_TEXTURE, texture);
 
-        self.gl.buffer_data_untyped(
-            gl::PIXEL_UNPACK_BUFFER,
-            new_size as isize,
-            ptr::null(),
-            gl::STREAM_DRAW,
-        );
-    }
-
-    pub fn update_texture_from_pbo(
-        &mut self,
-        texture: &Texture,
-        x0: u32,
-        y0: u32,
-        width: u32,
-        height: u32,
-        layer_index: i32,
-        stride: Option<u32>,
-        offset: usize,
-    ) {
-        debug_assert!(self.inside_frame);
-
-        let (gl_format, bpp, data_type) = match texture.format {
-            ImageFormat::A8 => (GL_FORMAT_A, 1, gl::UNSIGNED_BYTE),
-            ImageFormat::RGB8 => (gl::RGB, 3, gl::UNSIGNED_BYTE),
-            ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl()), 4, gl::UNSIGNED_BYTE),
-            ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
-            ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
-            ImageFormat::Invalid => unreachable!(),
+        let buffer = match self.upload_method {
+            UploadMethod::Immediate => None,
+            UploadMethod::PixelBuffer(hint) => {
+                let upload_size = upload_count * mem::size_of::<T>();
+                self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo.id);
+                if upload_size != 0 {
+                    self.gl.buffer_data_untyped(
+                        gl::PIXEL_UNPACK_BUFFER,
+                        upload_size as _,
+                        ptr::null(),
+                        hint.to_gl(),
+                    );
+                }
+                Some(PixelBuffer::new(hint.to_gl(), upload_size))
+            },
         };
 
-        let row_length = match stride {
-            Some(value) => value / bpp,
-            None => width,
-        };
-
-        if let Some(..) = stride {
-            self.gl
-                .pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
-        }
-
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-
-        match texture.target {
-            gl::TEXTURE_2D_ARRAY => {
-                self.gl.tex_sub_image_3d_pbo(
-                    texture.target,
-                    0,
-                    x0 as gl::GLint,
-                    y0 as gl::GLint,
-                    layer_index,
-                    width as gl::GLint,
-                    height as gl::GLint,
-                    1,
-                    gl_format,
-                    data_type,
-                    offset,
-                );
-            }
-            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
-                self.gl.tex_sub_image_2d_pbo(
-                    texture.target,
-                    0,
-                    x0 as gl::GLint,
-                    y0 as gl::GLint,
-                    width as gl::GLint,
-                    height as gl::GLint,
-                    gl_format,
-                    data_type,
-                    offset,
-                );
-            }
-            _ => panic!("BUG: Unexpected texture target!"),
-        }
-
-        // Reset row length to 0, otherwise the stride would apply to all texture uploads.
-        if let Some(..) = stride {
-            self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as gl::GLint);
+        TextureUploader {
+            target: UploadTarget {
+                gl: &*self.gl,
+                texture,
+            },
+            buffer,
+            marker: PhantomData,
         }
     }
 
     pub fn read_pixels(&mut self, width: i32, height: i32) -> Vec<u8> {
         self.gl.read_pixels(
-            0, 0, 
+            0, 0,
             width as i32, height as i32,
             gl::RGBA,
             gl::UNSIGNED_BYTE
         )
     }
 
     pub fn bind_vao(&mut self, vao: &VAO) {
         debug_assert!(self.inside_frame);
@@ -1762,8 +1709,177 @@ fn gl_texture_formats_for_image_format(
 }
 
 fn gl_type_for_texture_format(format: ImageFormat) -> gl::GLuint {
     match format {
         ImageFormat::RGBAF32 => gl::FLOAT,
         _ => gl::UNSIGNED_BYTE,
     }
 }
+
+
+struct UploadChunk {
+    rect: DeviceUintRect,
+    layer_index: i32,
+    stride: Option<u32>,
+    offset: usize,
+}
+
+struct PixelBuffer {
+    usage: gl::GLenum,
+    size_allocated: usize,
+    size_used: usize,
+    // small vector avoids heap allocation for a single chunk
+    chunks: SmallVec<[UploadChunk; 1]>,
+}
+
+impl PixelBuffer {
+    fn new(
+        usage: gl::GLenum,
+        size_allocated: usize,
+    ) -> Self {
+        PixelBuffer {
+            usage,
+            size_allocated,
+            size_used: 0,
+            chunks: SmallVec::new(),
+        }
+    }
+}
+
+struct UploadTarget<'a> {
+    gl: &'a gl::Gl,
+    texture: &'a Texture,
+}
+
+pub struct TextureUploader<'a, T> {
+    target: UploadTarget<'a>,
+    buffer: Option<PixelBuffer>,
+    marker: PhantomData<T>,
+}
+
+impl<'a, T> Drop for TextureUploader<'a, T> {
+    fn drop(&mut self) {
+        if let Some(buffer) = self.buffer.take() {
+            for chunk in buffer.chunks {
+                self.target.update_impl(chunk);
+            }
+            self.target.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+        }
+    }
+}
+
+impl<'a, T> TextureUploader<'a, T> {
+    pub fn upload(
+        &mut self,
+        rect: DeviceUintRect,
+        layer_index: i32,
+        stride: Option<u32>,
+        data: &[T],
+    ) {
+        match self.buffer {
+            Some(ref mut buffer) => {
+                let upload_size = mem::size_of::<T>() * data.len();
+                if buffer.size_used + upload_size > buffer.size_allocated {
+                    // flush
+                    for chunk in buffer.chunks.drain() {
+                        self.target.update_impl(chunk);
+                    }
+                    buffer.size_used = 0;
+                }
+
+                if upload_size > buffer.size_allocated {
+                    gl::buffer_data(
+                        self.target.gl,
+                        gl::PIXEL_UNPACK_BUFFER,
+                        data,
+                        buffer.usage,
+                    );
+                    buffer.size_allocated = upload_size;
+                } else {
+                    gl::buffer_sub_data(
+                        self.target.gl,
+                        gl::PIXEL_UNPACK_BUFFER,
+                        buffer.size_used as _,
+                        data,
+                    );
+                }
+
+                buffer.chunks.push(UploadChunk {
+                    rect, layer_index, stride,
+                    offset: buffer.size_used,
+                });
+                buffer.size_used += upload_size;
+            }
+            None => {
+                self.target.update_impl(UploadChunk {
+                    rect, layer_index, stride,
+                    offset: data.as_ptr() as _,
+                });
+            }
+        }
+    }
+}
+
+impl<'a> UploadTarget<'a> {
+    fn update_impl(&mut self, chunk: UploadChunk) {
+        let (gl_format, bpp, data_type) = match self.texture.format {
+            ImageFormat::A8 => (GL_FORMAT_A, 1, gl::UNSIGNED_BYTE),
+            ImageFormat::RGB8 => (gl::RGB, 3, gl::UNSIGNED_BYTE),
+            ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl), 4, gl::UNSIGNED_BYTE),
+            ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
+            ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
+            ImageFormat::Invalid => unreachable!(),
+        };
+
+        let row_length = match chunk.stride {
+            Some(value) => value / bpp,
+            None => self.texture.width,
+        };
+
+        if chunk.stride.is_some() {
+            self.gl.pixel_store_i(
+                gl::UNPACK_ROW_LENGTH,
+                row_length as _,
+            );
+        }
+
+        let pos = chunk.rect.origin;
+        let size = chunk.rect.size;
+
+        match self.texture.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_sub_image_3d_pbo(
+                    self.texture.target,
+                    0,
+                    pos.x as _,
+                    pos.y as _,
+                    chunk.layer_index,
+                    size.width as _,
+                    size.height as _,
+                    1,
+                    gl_format,
+                    data_type,
+                    chunk.offset,
+                );
+            }
+            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
+                self.gl.tex_sub_image_2d_pbo(
+                    self.texture.target,
+                    0,
+                    pos.x as _,
+                    pos.y as _,
+                    size.width as _,
+                    size.height as _,
+                    gl_format,
+                    data_type,
+                    chunk.offset,
+                );
+            }
+            _ => panic!("BUG: Unexpected texture target!"),
+        }
+
+        // Reset row length to 0, otherwise the stride would apply to all texture uploads.
+        if chunk.stride.is_some() {
+            self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
+        }
+    }
+}
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,33 +1,32 @@
 
 /* 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::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
 use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect};
-use api::{LayerSize, LayerVector2D};
-use api::{LayoutRect, LayoutSize};
+use api::{LayerSize, LayerVector2D, LayoutSize};
 use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
 use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
-use api::{ClipMode, TileOffset, TransformStyle, WorldPoint};
+use api::{TileOffset, TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_node::StickyFrameInfo;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
-use internal_types::{EdgeAaSegmentMask, FastHashMap, FastHashSet, RenderedDocument};
+use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
+use prim_store::{BrushAntiAliasMode};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
 use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
 use tiling::CompositeOps;
-use util::ComplexClipRegionHelpers;
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
@@ -37,21 +36,16 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF =
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: FrameBuilder,
     clip_scroll_tree: &'a mut ClipScrollTree,
     font_instances: FontInstanceMap,
     tiled_image_map: TiledImageMap,
     pipeline_epochs: Vec<(PipelineId, Epoch)>,
     replacements: Vec<(ClipId, ClipId)>,
-    /// Opaque rectangle vector, stored here in order to
-    /// avoid re-allocation on each use.
-    opaque_parts: Vec<LayoutRect>,
-    /// Same for the transparent rectangles.
-    transparent_parts: Vec<LayoutRect>,
     output_pipelines: &'a FastHashSet<PipelineId>,
 }
 
 impl<'a> FlattenContext<'a> {
     /// Since WebRender still handles fixed position and reference frame content internally
     /// we need to apply this table of id replacements only to the id that affects the
     /// position of a node. We can eventually remove this when clients start handling
     /// reference frames themselves. This method applies these replacements.
@@ -106,17 +100,18 @@ impl<'a> FlattenContext<'a> {
             if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
                     let info = LayerPrimitiveInfo::new(root_bounds);
                     self.builder.add_solid_rectangle(
                         ClipAndScrollInfo::simple(root_reference_frame_id),
                         &info,
                         bg_color,
-                        EdgeAaSegmentMask::empty(),
+                        None,
+                        BrushAntiAliasMode::Primitive,
                     );
                 }
             }
         }
 
 
         self.flatten_items(
             traversal,
@@ -443,28 +438,23 @@ impl<'a> FlattenContext<'a> {
                         );
                     }
                     None => {
                         warn!("Unknown font instance key: {:?}", text_info.font_key);
                     }
                 }
             }
             SpecificDisplayItem::Rectangle(ref info) => {
-                if !self.try_to_add_rectangle_splitting_on_clip(
+                self.builder.add_solid_rectangle(
+                    clip_and_scroll,
                     &prim_info,
                     info.color,
-                    &clip_and_scroll,
-                ) {
-                    self.builder.add_solid_rectangle(
-                        clip_and_scroll,
-                        &prim_info,
-                        info.color,
-                        EdgeAaSegmentMask::empty(),
-                    );
-                }
+                    None,
+                    BrushAntiAliasMode::Primitive,
+                );
             }
             SpecificDisplayItem::ClearRectangle => {
                 self.builder.add_clear_rectangle(
                     clip_and_scroll,
                     &prim_info,
                 );
             }
             SpecificDisplayItem::Line(ref info) => {
@@ -626,96 +616,16 @@ impl<'a> FlattenContext<'a> {
             }
             SpecificDisplayItem::PopAllShadows => {
                 self.builder.pop_all_shadows();
             }
         }
         None
     }
 
-    /// Try to optimize the rendering of a solid rectangle that is clipped by a single
-    /// rounded rectangle, by only masking the parts of the rectangle that intersect
-    /// the rounded parts of the clip. This is pretty simple now, so has a lot of
-    /// potential for further optimizations.
-    fn try_to_add_rectangle_splitting_on_clip(
-        &mut self,
-        info: &LayerPrimitiveInfo,
-        color: ColorF,
-        clip_and_scroll: &ClipAndScrollInfo,
-    ) -> bool {
-        if info.rect.size.area() < 200.0 { // arbitrary threshold
-            // too few pixels, don't bother adding instances
-            return false;
-        }
-        // If this rectangle is not opaque, splitting the rectangle up
-        // into an inner opaque region just ends up hurting batching and
-        // doing more work than necessary.
-        if color.a != 1.0 {
-            return false;
-        }
-
-        self.opaque_parts.clear();
-        self.transparent_parts.clear();
-
-        match info.local_clip {
-            LocalClip::Rect(_) => return false,
-            LocalClip::RoundedRect(_, ref region) => {
-                if region.mode == ClipMode::ClipOut {
-                    return false;
-                }
-                region.split_rectangles(
-                    &mut self.opaque_parts,
-                    &mut self.transparent_parts,
-                );
-            }
-        };
-
-        let local_clip = LocalClip::from(*info.local_clip.clip_rect());
-        let mut has_opaque = false;
-
-        for opaque in &self.opaque_parts {
-            let prim_info = LayerPrimitiveInfo {
-                rect: match opaque.intersection(&info.rect) {
-                    Some(rect) => rect,
-                    None => continue,
-                },
-                local_clip,
-                .. info.clone()
-            };
-            self.builder.add_solid_rectangle(
-                *clip_and_scroll,
-                &prim_info,
-                color,
-                EdgeAaSegmentMask::empty(),
-            );
-            has_opaque = true;
-        }
-
-        if !has_opaque {
-            return false
-        }
-
-        for transparent in &self.transparent_parts {
-            let prim_info = LayerPrimitiveInfo {
-                rect: match transparent.intersection(&info.rect) {
-                    Some(rect) => rect,
-                    None => continue,
-                },
-                .. info.clone()
-            };
-            self.builder.add_solid_rectangle(
-                *clip_and_scroll,
-                &prim_info,
-                color,
-                EdgeAaSegmentMask::empty(),
-            );
-        }
-        true
-    }
-
     /// Decomposes an image display item that is repeated into an image per individual repetition.
     /// We need to do this when we are unable to perform the repetition in the shader,
     /// for example if the image is tiled.
     ///
     /// In all of the "decompose" methods below, we independently handle horizontal and vertical
     /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
     /// decompositing the repetition horizontally while repeating vertically in the shader (for
     /// an image where the width is too bug but the height is not).
@@ -1117,18 +1027,16 @@ impl FrameContext {
                     background_color,
                     self.frame_builder_config,
                 ),
                 clip_scroll_tree: &mut self.clip_scroll_tree,
                 font_instances: resource_cache.get_font_instances(),
                 tiled_image_map: resource_cache.get_tiled_image_map(),
                 pipeline_epochs: Vec::new(),
                 replacements: Vec::new(),
-                opaque_parts: Vec::new(),
-                transparent_parts: Vec::new(),
                 output_pipelines,
             };
 
             roller.builder.push_root(
                 root_pipeline_id,
                 &root_pipeline.viewport_size,
                 &root_pipeline.content_size,
                 roller.clip_scroll_tree,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -16,23 +16,24 @@ use app_units::Au;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2};
 use frame::FrameId;
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCache;
-use internal_types::{EdgeAaSegmentMask, FastHashMap, FastHashSet};
+use gpu_types::ClipScrollNodeData;
+use internal_types::{FastHashMap, FastHashSet};
 use picture::{PictureCompositeMode, PictureKind, PicturePrimitive, RasterizationSpace};
-use prim_store::{TexelRect, YuvImagePrimitiveCpu};
+use prim_store::{BrushAntiAliasMode, BrushKind, BrushPrimitive, TexelRect, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex, SpecificPrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{RectangleContent, RectanglePrimitive, TextRunPrimitiveCpu};
+use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::ResourceCache;
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32};
 use tiling::{CompositeOps, Frame};
 use tiling::{RenderPass, RenderPassKind, RenderTargetKind};
 use tiling::{RenderTargetContext, ScrollbarPrimitive};
@@ -392,17 +393,17 @@ impl FrameBuilder {
                 .find(|sc| sc.transform_style == TransformStyle::Flat)
                 .map(|sc| sc.pic_prim_index)
                 .unwrap()
         } else {
             *self.picture_stack.last().unwrap()
         };
 
         // For each filter, create a new image with that composite mode.
-        for filter in &composite_ops.filters {
+        for filter in composite_ops.filters.iter().rev() {
             let src_prim = PicturePrimitive::new_image(
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
                 current_reference_frame_id,
                 None,
             );
             let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
@@ -751,77 +752,85 @@ impl FrameBuilder {
         mem::replace(&mut self.shadow_prim_stack, shadows);
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         color: ColorF,
-        edge_aa_segment_mask: EdgeAaSegmentMask,
+        segments: Option<Box<BrushSegmentDescriptor>>,
+        aa_mode: BrushAntiAliasMode,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             return;
         }
 
-        let prim = RectanglePrimitive {
-            content: RectangleContent::Fill(color),
-            edge_aa_segment_mask,
-        };
+        let prim = BrushPrimitive::new(
+            BrushKind::Solid {
+                color,
+            },
+            segments,
+            aa_mode,
+        );
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::Rectangle(prim),
+            PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_clear_rectangle(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
     ) {
-        let prim = RectanglePrimitive {
-            content: RectangleContent::Clear,
-            edge_aa_segment_mask: EdgeAaSegmentMask::empty(),
-        };
+        let prim = BrushPrimitive::new(
+            BrushKind::Clear,
+            None,
+            BrushAntiAliasMode::Primitive,
+        );
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::Rectangle(prim),
+            PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_scroll_bar(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         color: ColorF,
         scrollbar_info: ScrollbarInfo,
     ) {
         if color.a == 0.0 {
             return;
         }
 
-        let prim = RectanglePrimitive {
-            content: RectangleContent::Fill(color),
-            edge_aa_segment_mask: EdgeAaSegmentMask::empty(),
-        };
+        let prim = BrushPrimitive::new(
+            BrushKind::Solid {
+                color,
+            },
+            None,
+            BrushAntiAliasMode::Primitive,
+        );
 
         let prim_index = self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::Rectangle(prim),
+            PrimitiveContainer::Brush(prim),
         );
 
         self.scrollbar_prims.push(ScrollbarPrimitive {
             prim_index,
             clip_id: scrollbar_info.0,
             frame_rect: scrollbar_info.1,
         });
     }
@@ -1571,16 +1580,17 @@ impl FrameBuilder {
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_ratio: f32,
         scene_properties: &SceneProperties,
+        node_data: &[ClipScrollNodeData],
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.cpu_pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
@@ -1613,16 +1623,17 @@ impl FrameBuilder {
             &root_prim_context,
             true,
             &mut child_tasks,
             profile_counters,
             None,
             scene_properties,
             SpecificPrimitiveIndex(0),
             &self.screen_rect.to_i32(),
+            node_data,
         );
 
         let pic = &mut self.prim_store.cpu_pictures[0];
         pic.runs = prim_run_cmds;
 
         let root_render_task = RenderTask::new_picture(
             None,
             PrimitiveIndex(0),
@@ -1693,17 +1704,17 @@ impl FrameBuilder {
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
         gpu_cache.begin_frame();
 
-        let mut node_data = Vec::new();
+        let mut node_data = Vec::with_capacity(clip_scroll_tree.nodes.len());
         clip_scroll_tree.update_tree(
             &self.screen_rect.to_i32(),
             device_pixel_ratio,
             &mut self.clip_store,
             resource_cache,
             gpu_cache,
             pan,
             &mut node_data,
@@ -1718,16 +1729,17 @@ impl FrameBuilder {
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut profile_counters,
             device_pixel_ratio,
             scene_properties,
+            &node_data,
         );
 
         let mut passes = Vec::new();
         resource_cache.block_until_all_resources_added(gpu_cache, texture_cache_profile);
 
         if let Some(main_render_task_id) = main_render_task_id {
             let mut required_pass_count = 0;
             render_tasks.max_depth(main_render_task_id, 0, &mut required_pass_count);
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -77,19 +77,19 @@ impl FontTransform {
 
     pub fn determinant(&self) -> f64 {
         self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
     }
 
     pub fn compute_scale(&self) -> Option<(f64, f64)> {
         let det = self.determinant();
         if det != 0.0 {
-            let major = (self.scale_x as f64).hypot(self.skew_y as f64);
-            let minor = det.abs() / major;
-            Some((major, minor))
+            let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64);
+            let y_scale = det.abs() / x_scale;
+            Some((x_scale, y_scale))
         } else {
             None
         }
     }
 
     pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self {
         FontTransform::new(
             self.scale_x * scale_x,
@@ -188,16 +188,29 @@ impl FontInstance {
             FontRenderMode::Subpixel => {
                 if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
             }
             FontRenderMode::Bitmap => {
                 if color_bitmaps { GlyphFormat::ColorBitmap } else { GlyphFormat::Alpha }
             }
         }
     }
+
+    #[allow(dead_code)]
+    pub fn get_extra_strikes(&self, x_scale: f64) -> usize {
+        if self.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+            let mut bold_offset = self.size.to_f64_px() / 48.0;
+            if bold_offset < 1.0 {
+                bold_offset = 0.25 + 0.75 * bold_offset;
+            }
+            (bold_offset * x_scale).max(1.0).round() as usize
+        } else {
+            0
+        }
+    }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum GlyphFormat {
     Alpha,
     TransformedAlpha,
     Subpixel,
     TransformedSubpixel,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{LayerVector2D, LayerRect, LayerToWorldTransform, WorldToLayerTransform};
+use api::{LayerVector2D, LayerRect, LayerToWorldTransform};
 use gpu_cache::GpuCacheAddress;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 pub enum BlurDirection {
@@ -149,31 +149,31 @@ impl From<CompositePrimitiveInstance> fo
 #[repr(C)]
 pub struct BrushInstance {
     pub picture_address: RenderTaskAddress,
     pub prim_address: GpuCacheAddress,
     pub clip_id: ClipScrollNodeIndex,
     pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
     pub z: i32,
-    pub flags: i32,
+    pub segment_kind: i32,
     pub user_data0: i32,
     pub user_data1: i32,
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32,
                 instance.prim_address.as_int(),
                 ((instance.clip_id.0 as i32) << 16) | instance.scroll_id.0 as i32,
                 instance.clip_task_address.0 as i32,
                 instance.z,
-                instance.flags,
+                instance.segment_kind,
                 instance.user_data0,
                 instance.user_data1,
             ]
         }
     }
 }
 
 // Defines how a brush image is stretched onto the primitive.
@@ -181,37 +181,46 @@ impl From<BrushInstance> for PrimitiveIn
 // of the primitive, in which case this will be redundant.
 #[repr(C)]
 pub enum BrushImageKind {
     Simple = 0,     // A normal rect
     NinePatch = 1,  // A nine-patch image (stretch inside segments)
     Mirror = 2,     // A top left corner only (mirror across x/y axes)
 }
 
-#[derive(Copy, Debug, Clone)]
+#[derive(Copy, Debug, Clone, PartialEq)]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct ClipScrollNodeData {
     pub transform: LayerToWorldTransform,
-    pub inv_transform: WorldToLayerTransform,
+
+    /// Viewport rectangle clipped against parent viewport rectangles.  This is
+    /// in the coordinate system of the node origin.  Precisely, it combines the
+    /// local clipping rectangles of all the parent nodes on the way to the root,
+    /// including those of `ClipRegion` rectangles.  The combined clip is reset to
+    /// maximum when an incompatible coordinate system is encountered.
     pub local_clip_rect: LayerRect,
+
+    /// The scroll offset of all the nodes between us and our parent reference frame.
+    /// This is used to calculate intersections between us and content or nodes that
+    /// are also direct children of our reference frame.
     pub reference_frame_relative_scroll_offset: LayerVector2D,
+
     pub scroll_offset: LayerVector2D,
     pub transform_kind: f32,
     pub padding: [f32; 3],
 }
 
 impl ClipScrollNodeData {
     pub fn invalid() -> ClipScrollNodeData {
         ClipScrollNodeData {
             transform: LayerToWorldTransform::identity(),
-            inv_transform: WorldToLayerTransform::identity(),
             local_clip_rect: LayerRect::zero(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             scroll_offset: LayerVector2D::zero(),
             transform_kind: 0.0,
             padding: [0.0; 3],
         }
     }
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -185,23 +185,8 @@ pub enum ResultMsg {
     },
 }
 
 #[derive(Clone, Copy, Debug)]
 pub struct UvRect {
     pub uv0: DevicePoint,
     pub uv1: DevicePoint,
 }
-
-bitflags! {
-    /// Each bit of the edge AA mask is:
-    /// 0, when the edge of the primitive needs to be considered for AA
-    /// 1, when the edge of the segment needs to be considered for AA
-    ///
-    /// *Note*: the bit values have to match the shader logic in
-    /// `write_transform_vertex()` function.
-    pub struct EdgeAaSegmentMask: u8 {
-        const LEFT = 0x1;
-        const TOP = 0x2;
-        const RIGHT = 0x4;
-        const BOTTOM = 0x8;
-    }
-}
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -139,26 +139,27 @@ extern crate gleam;
 extern crate num_traits;
 extern crate plane_split;
 extern crate rayon;
 #[cfg(feature = "debugger")]
 #[macro_use]
 extern crate serde_derive;
 #[cfg(feature = "debugger")]
 extern crate serde_json;
+extern crate smallvec;
 extern crate time;
 #[cfg(feature = "debugger")]
 extern crate ws;
 #[cfg(feature = "debugger")]
 extern crate image;
 #[cfg(feature = "debugger")]
 extern crate base64;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
-pub use device::{build_shader_strings, ProgramCache};
+pub use device::{build_shader_strings, ProgramCache, UploadMethod, VertexUsageHint};
 pub use renderer::{CpuProfile, DebugFlags, GpuProfile, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, ReadPixelsFormat, Renderer, RendererOptions};
 pub use renderer::{RendererStats, ThreadListener};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -520,17 +520,17 @@ impl PicturePrimitive {
             }
         }
 
         if let Some(render_task_id) = self.render_task_id {
             parent_tasks.push(render_task_id);
         }
     }
 
-    pub fn write_gpu_blocks(&self, mut _request: GpuDataRequest) {
+    pub fn write_gpu_blocks(&self, _request: &mut GpuDataRequest) {
         // TODO(gw): We'll need to write the GPU blocks
         //           here specific to a brush primitive
         //           once we start drawing pictures as brushes!
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             PictureKind::TextShadow { .. } => RenderTargetKind::Color,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -58,17 +58,16 @@ fn supports_subpixel_aa() -> bool {
         8,
         4,
         &CGColorSpace::create_device_rgb(),
         kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
     );
     let ct_font = core_text::font::new_from_name("Helvetica", 16.).unwrap();
     cg_context.set_should_smooth_fonts(true);
     cg_context.set_should_antialias(true);
-    cg_context.set_allows_font_smoothing(true);
     cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
     let point = CGPoint { x: -1., y: 0. };
     let glyph = '|' as CGGlyph;
     ct_font.draw_glyphs(&[glyph], &[point], cg_context.clone());
     let data = cg_context.data();
     data[0] != data[1] || data[1] != data[2]
 }
 
@@ -79,16 +78,17 @@ fn should_use_white_on_black(color: Colo
 }
 
 fn get_glyph_metrics(
     ct_font: &CTFont,
     transform: Option<&CGAffineTransform>,
     glyph: CGGlyph,
     x_offset: f64,
     y_offset: f64,
+    extra_width: f64,
 ) -> GlyphMetrics {
     let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);
 
     if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() ||
         bounds.size.height.is_nan()
     {
         // If an unexpected glyph index is requested, core text will return NaN values
         // which causes us to do bad thing as the value is cast into an integer and
@@ -103,16 +103,23 @@ fn get_glyph_metrics(
             rasterized_descent: 0,
             advance: 0.0,
         };
     }
 
     let mut advance = CGSize { width: 0.0, height: 0.0 };
     ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
 
+    if bounds.size.width > 0.0 {
+        bounds.size.width += extra_width;
+    }
+    if advance.width > 0.0 {
+        advance.width += extra_width;
+    }
+
     if let Some(transform) = transform {
         bounds = bounds.apply_transform(transform);
         advance = advance.apply_transform(transform);
     }
 
     // First round out to pixel boundaries
     // CG Origin is bottom left
     let mut left = bounds.origin.x.floor() as i32;
@@ -340,17 +347,17 @@ impl FontContext {
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         self.get_ct_font(font.font_key, font.size, &font.variations)
             .and_then(|ref ct_font| {
                 let glyph = key.index as CGGlyph;
                 let (x_offset, y_offset) = font.get_subpx_offset(key);
-                let metrics = get_glyph_metrics(ct_font, None, glyph, x_offset, y_offset);
+                let metrics = get_glyph_metrics(ct_font, None, glyph, x_offset, y_offset, 0.0);
                 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,
@@ -438,39 +445,47 @@ impl FontContext {
         }
     }
 
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
-        let (.., minor) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
-        let size = font.size.scale_by(minor as f32);
+        let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+        let size = font.size.scale_by(y_scale as f32);
         let ct_font = match self.get_ct_font(font.font_key, size, &font.variations) {
             Some(font) => font,
             None => return None,
         };
 
-        let shape = font.transform.pre_scale(minor.recip() as f32, minor.recip() as f32);
+        let shape = font.transform.pre_scale(y_scale.recip() as f32, y_scale.recip() as f32);
         let transform = if shape.is_identity() {
             None
         } else {
             Some(CGAffineTransform {
                 a: shape.scale_x as f64,
                 b: -shape.skew_y as f64,
                 c: -shape.skew_x as f64,
                 d: shape.scale_y as f64,
                 tx: 0.0,
                 ty: 0.0
             })
         };
         let glyph = key.index as CGGlyph;
         let (x_offset, y_offset) = font.get_subpx_offset(key);
-        let metrics = get_glyph_metrics(&ct_font, transform.as_ref(), glyph, x_offset, y_offset);
+        let extra_strikes = font.get_extra_strikes(x_scale);
+        let metrics = get_glyph_metrics(
+            &ct_font,
+            transform.as_ref(),
+            glyph,
+            x_offset,
+            y_offset,
+            extra_strikes as f64 * y_scale / x_scale,
+        );
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return None;
         }
 
         // The result of this function, in all render modes, is going to be a
         // BGRA surface with white text on transparency using premultiplied
         // alpha. For subpixel text, the RGB values will be the mask value for
         // the individual components. For bitmap glyphs, the RGB values will be
@@ -553,19 +568,17 @@ impl FontContext {
         // 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);
 
         // Don't quantize because we're doing it already.
         cg_context.set_allows_font_subpixel_quantization(false);
         cg_context.set_should_subpixel_quantize_fonts(false);
 
-        cg_context.set_allows_font_smoothing(smooth);
         cg_context.set_should_smooth_fonts(smooth);
-        cg_context.set_allows_antialiasing(antialias);
         cg_context.set_should_antialias(antialias);
 
         // Fill the background. This could be opaque white, opaque black, or
         // transparency.
         cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_alpha);
         let rect = CGRect {
             origin: CGPoint { x: 0.0, y: 0.0 },
             size: CGSize {
@@ -586,17 +599,27 @@ impl FontContext {
         };
 
         if let Some(transform) = transform {
             cg_context.set_text_matrix(&transform);
 
             draw_origin = draw_origin.apply_transform(&transform.invert());
         }
 
-        ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
+        if extra_strikes > 0 {
+            let strikes = 1 + extra_strikes;
+            let pixel_step = y_scale / x_scale;
+            let glyphs = vec![glyph; strikes];
+            let origins = (0..strikes)
+                .map(|i| CGPoint { x: draw_origin.x + i as f64 * pixel_step, y: draw_origin.y })
+                .collect::<Vec<_>>();
+            ct_font.draw_glyphs(&glyphs, &origins, cg_context.clone());
+        } else {
+            ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
+        }
 
         let mut rasterized_pixels = cg_context.data().to_vec();
 
         if font.render_mode != FontRenderMode::Bitmap {
             // We rendered text into an opaque surface. The code below needs to
             // ignore the current value of each pixel's alpha channel. But it's
             // allowed to write to the alpha channel, because we're done calling
             // CG functions now.
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -170,17 +170,17 @@ impl FontContext {
                     load_flags |= FT_LOAD_FORCE_AUTOHINT;
                 }
             }
         }
 
         if font.flags.contains(FontInstanceFlags::NO_AUTOHINT) {
             load_flags |= FT_LOAD_NO_AUTOHINT;
         }
-        if font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) {
+        if !font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) {
             load_flags |= FT_LOAD_NO_BITMAP;
         }
         if font.flags.contains(FontInstanceFlags::VERTICAL_LAYOUT) {
             load_flags |= FT_LOAD_VERTICAL_LAYOUT;
         }
 
         load_flags |= FT_LOAD_COLOR;
         load_flags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
@@ -189,30 +189,30 @@ impl FontContext {
         let mut result = if font.render_mode == FontRenderMode::Bitmap {
             if (load_flags & FT_LOAD_NO_BITMAP) != 0 {
                 FT_Error(FT_Err_Cannot_Render_Glyph as i32)
             } else {
                 unsafe { FT_Set_Transform(face.face, ptr::null_mut(), ptr::null_mut()) };
                 self.choose_bitmap_size(face.face, req_size)
             }
         } else {
-            let (major, minor) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
-            let shape = font.transform.pre_scale(major.recip() as f32, minor.recip() as f32);
+            let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+            let shape = font.transform.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32);
             let mut ft_shape = FT_Matrix {
                 xx: (shape.scale_x * 65536.0) as FT_Fixed,
                 xy: (shape.skew_x * -65536.0) as FT_Fixed,
                 yx: (shape.skew_y * -65536.0) as FT_Fixed,
                 yy: (shape.scale_y * 65536.0) as FT_Fixed,
             };
             unsafe {
                 FT_Set_Transform(face.face, &mut ft_shape, ptr::null_mut());
                 FT_Set_Char_Size(
                     face.face,
-                    (req_size * major * 64.0 + 0.5) as FT_F26Dot6,
-                    (req_size * minor * 64.0 + 0.5) as FT_F26Dot6,
+                    (req_size * x_scale * 64.0 + 0.5) as FT_F26Dot6,
+                    (req_size * y_scale * 64.0 + 0.5) as FT_F26Dot6,
                     0,
                     0,
                 )
             }
         };
 
         if result.succeeded() {
             result = unsafe { FT_Load_Glyph(face.face, glyph.index as FT_UInt, load_flags as FT_Int32) };
@@ -488,19 +488,20 @@ impl FontContext {
             None => return None,
         };
 
         // Get dimensions of the glyph, to see if we need to rasterize it.
         let dimensions = match self.get_glyph_dimensions_impl(slot, font, key, false) {
             Some(val) => val,
             None => return None,
         };
+        let GlyphDimensions { mut left, mut top, width, height, .. } = dimensions;
 
         // For spaces and other non-printable characters, early out.
-        if dimensions.width == 0 || dimensions.height == 0 {
+        if width == 0 || height == 0 {
             return None;
         }
 
         let format = unsafe { (*slot).format };
         let mut scale = 1.0;
         match format {
             FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
                 let y_size = unsafe { (*(*(*slot).face).size).metrics.y_ppem };
@@ -510,44 +511,43 @@ impl FontContext {
                 if !self.rasterize_glyph_outline(slot, font, key) {
                     return None;
                 }
             }
             _ => {
                 error!("Unsupported {:?}", format);
                 return None;
             }
-        }
+        };
 
-        let bitmap = unsafe { &(*slot).bitmap };
-        let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
         info!(
             "Rasterizing {:?} as {:?} with dimensions {:?}",
             key,
             font.render_mode,
             dimensions
         );
 
+        let bitmap = unsafe { &(*slot).bitmap };
+        let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
         let (actual_width, actual_height) = match pixel_mode {
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
                 assert!(bitmap.width % 3 == 0);
                 ((bitmap.width / 3) as i32, bitmap.rows as i32)
             }
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
                 assert!(bitmap.rows % 3 == 0);
                 (bitmap.width as i32, (bitmap.rows / 3) as i32)
             }
             FT_Pixel_Mode::FT_PIXEL_MODE_MONO |
             FT_Pixel_Mode::FT_PIXEL_MODE_GRAY |
             FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
                 (bitmap.width as i32, bitmap.rows as i32)
             }
             _ => panic!("Unsupported {:?}", pixel_mode),
         };
-        let (left, top) = unsafe { ((*slot).bitmap_left, (*slot).bitmap_top) };
         let mut final_buffer = vec![0; (actual_width * actual_height * 4) as usize];
 
         // Extract the final glyph from FT format into RGBA8 format, which is
         // what WR expects.
         let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR);
         let mut src_row = bitmap.buffer;
         let mut dest: usize = 0;
         while dest < final_buffer.len() {
@@ -620,19 +620,29 @@ impl FontContext {
                     dest_slice.copy_from_slice(src_slice);
                 }
                 _ => panic!("Unsupported {:?}", pixel_mode),
             }
             src_row = unsafe { src_row.offset(bitmap.pitch as isize) };
             dest = row_end;
         }
 
+        match format {
+            FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
+                unsafe {
+                    left += (*slot).bitmap_left;
+                    top += (*slot).bitmap_top - actual_height;
+                }
+            }
+            _ => {}
+        }
+
         Some(RasterizedGlyph {
-            left: (dimensions.left + left) as f32,
-            top: (dimensions.top + top - actual_height) as f32,
+            left: left as f32,
+            top: top as f32,
             width: actual_width as u32,
             height: actual_height as u32,
             scale,
             format: font.get_glyph_format(pixel_mode == FT_Pixel_Mode::FT_PIXEL_MODE_BGRA),
             bytes: final_buffer,
         })
     }
 }
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -180,18 +180,18 @@ impl FontContext {
         let face = self.get_font_face(font);
         let glyph = key.index as u16;
         let advance = 0.0f32;
         let offset = dwrote::GlyphOffset {
             advanceOffset: 0.0,
             ascenderOffset: 0.0,
         };
 
-        let (.., minor) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
-        let size = (font.size.to_f64_px() * minor) as f32;
+        let (.., y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+        let size = (font.size.to_f64_px() * y_scale) as f32;
 
         let glyph_run = dwrote::DWRITE_GLYPH_RUN {
             fontFace: unsafe { face.as_ptr() },
             fontEmSize: size, // size in DIPs (1/96", same as CSS pixels)
             glyphCount: 1,
             glyphIndices: &glyph,
             glyphAdvances: &advance,
             glyphOffsets: &offset,
@@ -203,17 +203,17 @@ impl FontContext {
         let dwrite_render_mode = dwrite_render_mode(
             face,
             font,
             size,
             dwrite_measure_mode,
         );
 
         let (x_offset, y_offset) = font.get_subpx_offset(key);
-        let shape = font.transform.pre_scale(minor.recip() as f32, minor.recip() as f32);
+        let shape = font.transform.pre_scale(y_scale.recip() as f32, y_scale.recip() as f32);
         let transform = dwrote::DWRITE_MATRIX {
             m11: shape.scale_x,
             m12: shape.skew_y,
             m21: shape.skew_x,
             m22: shape.scale_y,
             dx: x_offset as f32,
             dy: y_offset as f32,
         };
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,36 +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::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect};
-use api::{DevicePoint, ExtendMode, FontRenderMode, GlyphInstance, GlyphKey};
-use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect};
-use api::{ClipMode, LayerSize, LayerVector2D, LayerToWorldTransform, LineOrientation, LineStyle};
-use api::{ClipAndScrollInfo, PremultipliedColorF, TileOffset};
-use api::{ClipId, LayerTransform, PipelineId, YuvColorSpace, YuvFormat};
+use api::{BorderRadius, BuiltDisplayList, ClipAndScrollInfo, ClipId, ClipMode, ColorF};
+use api::{ComplexClipRegion, DeviceIntRect, DevicePoint, ExtendMode, FontRenderMode};
+use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
+use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
+use api::{LineStyle, PipelineId, PremultipliedColorF, TileOffset, WorldToLayerTransform};
+use api::{YuvColorSpace, YuvFormat};
 use border::BorderCornerInstance;
 use clip_scroll_tree::{CoordinateSystemId, ClipScrollTree};
-use clip::{ClipSourcesHandle, ClipStore};
+use clip::{ClipSource, ClipSourcesHandle, ClipStore};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
-use internal_types::{EdgeAaSegmentMask, FastHashMap};
+use internal_types::{FastHashMap};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
+use gpu_types::ClipScrollNodeData;
 use picture::{PictureKind, PicturePrimitive, RasterizationSpace};
 use profiler::FrameProfileCounters;
 use render_task::{ClipChainNode, ClipChainNodeIter, ClipWorkItem, RenderTask, RenderTaskId};
 use render_task::RenderTaskTree;
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use resource_cache::{ImageProperties, ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
-use std::{mem, usize};
+use std::{mem, u16, usize};
 use std::rc::Rc;
-use util::{pack_as_float, recycle_vec, MatrixHelpers, TransformedRect, TransformedRectKind};
+use util::{MatrixHelpers, calculate_screen_bounding_rect, extract_inner_rect_safe, pack_as_float};
+use util::recycle_vec;
+
+const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0;
 
 #[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
     pub clip_and_scroll: ClipAndScrollInfo,
 }
 
@@ -131,17 +135,16 @@ pub struct DeferredResolve {
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct SpecificPrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct PrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
-    Rectangle,
     TextRun,
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     Line,
@@ -182,64 +185,204 @@ pub struct PrimitiveMetadata {
     pub is_backface_visible: bool,
     pub screen_rect: Option<DeviceIntRect>,
 
     /// A tag used to identify this primitive outside of WebRender. This is
     /// used for returning useful data during hit testing.
     pub tag: Option<ItemTag>,
 }
 
-#[derive(Debug,Clone,Copy)]
-pub enum RectangleContent {
-    Fill(ColorF),
-    Clear,
-}
-
-#[derive(Debug)]
-pub struct RectanglePrimitive {
-    pub content: RectangleContent,
-    pub edge_aa_segment_mask: EdgeAaSegmentMask,
-}
-
-impl ToGpuBlocks for RectanglePrimitive {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.push(match self.content {
-            RectangleContent::Fill(color) => color.premultiplied(),
-            // Opaque black with operator dest out
-            RectangleContent::Clear => PremultipliedColorF::BLACK,
-        });
-        request.extend_from_slice(&[GpuBlockData {
-            data: [self.edge_aa_segment_mask.bits() as f32, 0.0, 0.0, 0.0],
-        }]);
-    }
-}
-
 #[derive(Debug)]
 pub enum BrushMaskKind {
     //Rect,         // TODO(gw): Optimization opportunity for masks with 0 border radii.
     Corner(LayerSize),
     RoundedRect(LayerRect, BorderRadius),
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
     Mask {
         clip_mode: ClipMode,
         kind: BrushMaskKind,
+    },
+    Solid {
+        color: ColorF,
+    },
+    Clear,
+}
+
+#[derive(Debug, Copy, Clone)]
+#[repr(u32)]
+pub enum BrushAntiAliasMode {
+    Primitive = 0,
+    Segment = 1,
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Copy, Clone)]
+#[repr(C)]
+pub enum BrushSegmentKind {
+    TopLeft = 0,
+    TopRight,
+    BottomRight,
+    BottomLeft,
+
+    TopMid,
+    MidRight,
+    BottomMid,
+    MidLeft,
+
+    Center,
+}
+
+#[derive(Debug)]
+pub struct BrushSegment {
+    pub local_rect: LayerRect,
+    pub clip_task_id: Option<RenderTaskId>,
+}
+
+impl BrushSegment {
+    fn new(
+        origin: LayerPoint,
+        size: LayerSize,
+    ) -> BrushSegment {
+        BrushSegment {
+            local_rect: LayerRect::new(origin, size),
+            clip_task_id: None,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct BrushSegmentDescriptor {
+    pub top_left_offset: LayerVector2D,
+    pub bottom_right_offset: LayerVector2D,
+    pub segments: [BrushSegment; 9],
+    pub enabled_segments: u16,
+    pub can_optimize_clip_mask: bool,
+}
+
+impl BrushSegmentDescriptor {
+    pub fn new(
+        outer_rect: &LayerRect,
+        inner_rect: &LayerRect,
+        valid_segments: Option<&[BrushSegmentKind]>,
+    ) -> BrushSegmentDescriptor {
+        let p0 = outer_rect.origin;
+        let p1 = inner_rect.origin;
+        let p2 = inner_rect.bottom_right();
+        let p3 = outer_rect.bottom_right();
+
+        let enabled_segments = match valid_segments {
+            Some(valid_segments) => {
+                valid_segments.iter().fold(
+                    0,
+                    |acc, segment| acc | 1 << *segment as u32
+                )
+            }
+            None => u16::MAX,
+        };
+
+        BrushSegmentDescriptor {
+            enabled_segments,
+            can_optimize_clip_mask: false,
+            top_left_offset: p1 - p0,
+            bottom_right_offset: p3 - p2,
+            segments: [
+                BrushSegment::new(
+                    LayerPoint::new(p0.x, p0.y),
+                    LayerSize::new(p1.x - p0.x, p1.y - p0.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p2.x, p0.y),
+                    LayerSize::new(p3.x - p2.x, p1.y - p0.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p2.x, p2.y),
+                    LayerSize::new(p3.x - p2.x, p3.y - p2.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p0.x, p2.y),
+                    LayerSize::new(p1.x - p0.x, p3.y - p2.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p1.x, p0.y),
+                    LayerSize::new(p2.x - p1.x, p1.y - p0.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p2.x, p1.y),
+                    LayerSize::new(p3.x - p2.x, p2.y - p1.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p1.x, p2.y),
+                    LayerSize::new(p2.x - p1.x, p3.y - p2.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p0.x, p1.y),
+                    LayerSize::new(p1.x - p0.x, p2.y - p1.y),
+                ),
+                BrushSegment::new(
+                    LayerPoint::new(p1.x, p1.y),
+                    LayerSize::new(p2.x - p1.x, p2.y - p1.y),
+                ),
+            ],
+        }
     }
 }
 
 #[derive(Debug)]
 pub struct BrushPrimitive {
     pub kind: BrushKind,
+    pub segment_desc: Option<Box<BrushSegmentDescriptor>>,
+    pub aa_mode: BrushAntiAliasMode,
+}
+
+impl BrushPrimitive {
+    pub fn new(
+        kind: BrushKind,
+        segment_desc: Option<Box<BrushSegmentDescriptor>>,
+        aa_mode: BrushAntiAliasMode,
+    ) -> BrushPrimitive {
+        BrushPrimitive {
+            kind,
+            segment_desc,
+            aa_mode,
+        }
+    }
 }
 
 impl ToGpuBlocks for BrushPrimitive {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
+        match self.segment_desc {
+            Some(ref segment_desc) => {
+                request.push([
+                    segment_desc.top_left_offset.x,
+                    segment_desc.top_left_offset.y,
+                    segment_desc.bottom_right_offset.x,
+                    segment_desc.bottom_right_offset.y,
+                ]);
+            }
+            None => {
+                request.push([0.0; 4]);
+            }
+        }
+        request.push([
+            self.aa_mode as u32 as f32,
+            0.0,
+            0.0,
+            0.0,
+        ]);
         match self.kind {
+            BrushKind::Solid { color } => {
+                request.push(color.premultiplied());
+            }
+            BrushKind::Clear => {
+                // Opaque black with operator dest out
+                request.push(PremultipliedColorF::BLACK);
+            }
             BrushKind::Mask { clip_mode, kind: BrushMaskKind::Corner(radius) } => {
                 request.push([
                     radius.width,
                     radius.height,
                     clip_mode as u32 as f32,
                     0.0,
                 ]);
             }
@@ -858,65 +1001,61 @@ impl ClipData {
         ] {
             corner.write(request);
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
-    Rectangle(RectanglePrimitive),
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     RadialGradient(RadialGradientPrimitiveCpu),
     Picture(PicturePrimitive),
     Line(LinePrimitive),
     Brush(BrushPrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
-    pub cpu_rectangles: Vec<RectanglePrimitive>,
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_pictures: Vec<PicturePrimitive>,
     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_lines: Vec<LinePrimitive>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
-            cpu_rectangles: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_pictures: 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_lines: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
-            cpu_rectangles: recycle_vec(self.cpu_rectangles),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_pictures: recycle_vec(self.cpu_pictures),
             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),
@@ -940,42 +1079,30 @@ impl PrimitiveStore {
             gpu_location: GpuCacheHandle::new(),
             clip_task_id: None,
             local_rect: *local_rect,
             local_clip_rect: *local_clip_rect,
             is_backface_visible: is_backface_visible,
             screen_rect: None,
             tag,
             opacity: PrimitiveOpacity::translucent(),
-            prim_kind: PrimitiveKind::Rectangle,
+            prim_kind: PrimitiveKind::Brush,
             cpu_prim_index: SpecificPrimitiveIndex(0),
         };
 
         let metadata = match container {
-            PrimitiveContainer::Rectangle(rect) => {
-                let opacity = match &rect.content {
-                    &RectangleContent::Fill(ref color) => {
-                        PrimitiveOpacity::from_alpha(color.a)
-                    },
-                    &RectangleContent::Clear => PrimitiveOpacity::opaque()
+            PrimitiveContainer::Brush(brush) => {
+                let opacity = match brush.kind {
+                    BrushKind::Clear => PrimitiveOpacity::translucent(),
+                    BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
+                    BrushKind::Mask { .. } => PrimitiveOpacity::translucent(),
                 };
+
                 let metadata = PrimitiveMetadata {
                     opacity,
-                    prim_kind: PrimitiveKind::Rectangle,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_rectangles.push(rect);
-
-                metadata
-            }
-            PrimitiveContainer::Brush(brush) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
                     prim_kind: PrimitiveKind::Brush,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
 
                 self.cpu_brushes.push(brush);
 
                 metadata
@@ -1104,17 +1231,17 @@ impl PrimitiveStore {
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         child_tasks: Vec<RenderTaskId>,
         parent_tasks: &mut Vec<RenderTaskId>,
         pic_index: SpecificPrimitiveIndex,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
-            PrimitiveKind::Rectangle | PrimitiveKind::Border | PrimitiveKind::Line => {}
+            PrimitiveKind::Border | PrimitiveKind::Line => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
                         prim_context,
                         render_tasks,
                         metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
                         child_tasks,
@@ -1164,32 +1291,28 @@ impl PrimitiveStore {
                     resource_cache.request_image(
                         image_cpu.yuv_key[channel],
                         image_cpu.image_rendering,
                         None,
                         gpu_cache,
                     );
                 }
             }
+            PrimitiveKind::Brush |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
-            PrimitiveKind::RadialGradient |
-            PrimitiveKind::Brush => {}
+            PrimitiveKind::RadialGradient => {}
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = gpu_cache.request(&mut metadata.gpu_location) {
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
-                PrimitiveKind::Rectangle => {
-                    let rect = &self.cpu_rectangles[metadata.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[metadata.cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
@@ -1213,18 +1336,33 @@ impl PrimitiveStore {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
                     gradient.build_gpu_blocks_for_angle_radial(prim_context.display_list, request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Picture => {
+                    // TODO(gw): This is a bit of a hack. The Picture type
+                    //           is drawn by the brush_image shader, so the
+                    //           layout here needs to conform to the same
+                    //           BrushPrimitive layout. We should tidy this
+                    //           up in the future so it's enforced that these
+                    //           types use a shared function to write out the
+                    //           GPU blocks...
+                    request.push([0.0; 4]);
+                    request.push([
+                        BrushAntiAliasMode::Primitive as u32 as f32,
+                        0.0,
+                        0.0,
+                        0.0,
+                    ]);
+
                     self.cpu_pictures[metadata.cpu_prim_index.0]
-                        .write_gpu_blocks(request);
+                        .write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                     brush.write_gpu_blocks(request);
                 }
             }
         }
     }
@@ -1235,16 +1373,17 @@ impl PrimitiveStore {
         prim_context: &PrimitiveContext,
         prim_screen_rect: &DeviceIntRect,
         screen_rect: &DeviceIntRect,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
         tasks: &mut Vec<RenderTaskId>,
+        node_data: &[ClipScrollNodeData],
     ) -> bool {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         metadata.clip_task_id = None;
 
         let prim_screen_rect = match prim_screen_rect.intersection(screen_rect) {
             Some(rect) => rect,
             None => {
                 metadata.screen_rect = None;
@@ -1339,27 +1478,122 @@ impl PrimitiveStore {
             // optimization of the empty mask.
             combined_inner_rect = DeviceIntRect::zero();
         }
 
         if combined_inner_rect.contains_rect(&prim_screen_rect) {
            return true;
         }
 
-        let clip_task = RenderTask::new_mask(
-            None,
-            combined_outer_rect,
-            combined_inner_rect,
-            clips,
-            clip_store,
-            transform.transform_kind() == TransformedRectKind::AxisAligned,
-            prim_coordinate_system_id,
-        );
+        let mut needs_prim_clip_task = true;
+
+        if metadata.prim_kind == PrimitiveKind::Brush {
+            let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
+            if brush.segment_desc.is_none() && metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA {
+                if let BrushKind::Solid { .. } = brush.kind {
+                    if clips.len() == 1 {
+                        let clip_item = clips.first().unwrap();
+                        if clip_item.coordinate_system_id == prim_coordinate_system_id {
+                            let local_clips = clip_store.get_opt(&clip_item.clip_sources).expect("bug");
+                            let mut selected_clip = None;
+                            for &(ref clip, _) in &local_clips.clips {
+                                match *clip {
+                                    ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) => {
+                                        if selected_clip.is_some() {
+                                            selected_clip = None;
+                                            break;
+                                        }
+                                        selected_clip = Some((rect, radii, clip_item.scroll_node_data_index));
+                                    }
+                                    ClipSource::Rectangle(..) => {}
+                                    ClipSource::RoundedRectangle(_, _, ClipMode::ClipOut) |
+                                    ClipSource::BorderCorner(..) |
+                                    ClipSource::Image(..) => {
+                                        selected_clip = None;
+                                        break;
+                                    }
+                                }
+                            }
+                            if let Some((rect, radii, clip_scroll_node_data_index)) = selected_clip {
+                                // If the scroll node transforms are different between the clip
+                                // node and the primitive, we need to get the clip rect in the
+                                // local space of the primitive, in order to generate correct
+                                // local segments.
+                                let local_clip_rect = if clip_scroll_node_data_index == prim_context.scroll_node.node_data_index {
+                                    rect
+                                } else {
+                                    let clip_transform_data = &node_data[clip_scroll_node_data_index.0 as usize];
+                                    let prim_transform = &prim_context.scroll_node.world_content_transform;
+
+                                    let relative_transform = prim_transform
+                                        .inverse()
+                                        .unwrap_or(WorldToLayerTransform::identity())
+                                        .pre_mul(&clip_transform_data.transform);
 
-        if let Some(clip_task) = clip_task {
+                                    relative_transform.transform_rect(&rect)
+                                };
+                                brush.segment_desc = create_nine_patch(
+                                    &metadata.local_rect,
+                                    &local_clip_rect,
+                                    &radii
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+
+            if let Some(ref mut segment_desc) = brush.segment_desc {
+                let enabled_segments = segment_desc.enabled_segments;
+                let can_optimize_clip_mask = segment_desc.can_optimize_clip_mask;
+
+                for (i, segment) in segment_desc.segments.iter_mut().enumerate() {
+                    // We only build clips for the corners. The ordering of the
+                    // BrushSegmentKind enum is such that corners come first, then
+                    // edges, then inner.
+                    let segment_enabled = ((1 << i) & enabled_segments) != 0;
+                    let create_clip_task = segment_enabled &&
+                                           (!can_optimize_clip_mask || i <= BrushSegmentKind::BottomLeft as usize);
+                    segment.clip_task_id = if create_clip_task {
+                        let segment_screen_rect = calculate_screen_bounding_rect(
+                            &prim_context.scroll_node.world_content_transform,
+                            &segment.local_rect,
+                            prim_context.device_pixel_ratio
+                        );
+
+                        combined_outer_rect.intersection(&segment_screen_rect).map(|bounds| {
+                            let clip_task = RenderTask::new_mask(
+                                None,
+                                bounds,
+                                clips.clone(),
+                                prim_coordinate_system_id,
+                            );
+
+                            let clip_task_id = render_tasks.add(clip_task);
+                            tasks.push(clip_task_id);
+
+                            clip_task_id
+                        })
+                    } else {
+                        None
+                    };
+                }
+
+                needs_prim_clip_task = false;
+            }
+        }
+
+        if needs_prim_clip_task {
+            let clip_task = RenderTask::new_mask(
+                None,
+                combined_outer_rect,
+                clips,
+                prim_coordinate_system_id,
+            );
+
             let clip_task_id = render_tasks.add(clip_task);
             metadata.clip_task_id = Some(clip_task_id);
             tasks.push(clip_task_id);
         }
 
         true
     }
 
@@ -1374,16 +1608,17 @@ impl PrimitiveStore {
         clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         perform_culling: bool,
         parent_tasks: &mut Vec<RenderTaskId>,
         scene_properties: &SceneProperties,
         profile_counters: &mut FrameProfileCounters,
         pic_index: SpecificPrimitiveIndex,
         screen_rect: &DeviceIntRect,
+        node_data: &[ClipScrollNodeData],
     ) -> Option<LayerRect> {
         // Reset the visibility of this primitive.
         // Do some basic checks first, that can early out
         // without even knowing the local rect.
         let (cpu_prim_index, dependencies, cull_children, may_need_clip_mask) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             metadata.screen_rect = None;
 
@@ -1440,16 +1675,17 @@ impl PrimitiveStore {
                 prim_context,
                 cull_children,
                 &mut child_tasks,
                 profile_counters,
                 rfid,
                 scene_properties,
                 cpu_prim_index,
                 screen_rect,
+                node_data,
             );
 
             let metadata = &mut self.cpu_metadata[prim_index.0];
 
             // Restore the dependencies (borrow check dance)
             let pic = &mut self.cpu_pictures[cpu_prim_index.0];
             pic.runs = dependencies;
 
@@ -1469,42 +1705,43 @@ impl PrimitiveStore {
 
             let local_rect = metadata.local_rect.intersection(&metadata.local_clip_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
                 None if perform_culling => return None,
                 None => LayerRect::zero(),
             };
 
-            let xf_rect = TransformedRect::new(
+            let screen_bounding_rect = calculate_screen_bounding_rect(
+                &prim_context.scroll_node.world_content_transform,
                 &local_rect,
-                &prim_context.scroll_node.world_content_transform,
                 prim_context.device_pixel_ratio
             );
 
             let clip_bounds = &prim_context.clip_node.combined_clip_outer_bounds;
-            metadata.screen_rect = xf_rect.bounding_rect.intersection(clip_bounds);
+            metadata.screen_rect = screen_bounding_rect.intersection(clip_bounds);
 
             if metadata.screen_rect.is_none() && perform_culling {
                 return None;
             }
 
-            (local_rect, xf_rect.bounding_rect)
+            (local_rect, screen_bounding_rect)
         };
 
         if perform_culling && may_need_clip_mask && !self.update_clip_task(
             prim_index,
             prim_context,
             &unclipped_device_rect,
             screen_rect,
             resource_cache,
             gpu_cache,
             render_tasks,
             clip_store,
             parent_tasks,
+            node_data,
         ) {
             return None;
         }
 
         self.prepare_prim_for_render_inner(
             prim_index,
             prim_context,
             resource_cache,
@@ -1539,16 +1776,17 @@ impl PrimitiveStore {
         parent_prim_context: &PrimitiveContext,
         perform_culling: bool,
         parent_tasks: &mut Vec<RenderTaskId>,
         profile_counters: &mut FrameProfileCounters,
         original_reference_frame_id: Option<ClipId>,
         scene_properties: &SceneProperties,
         pic_index: SpecificPrimitiveIndex,
         screen_rect: &DeviceIntRect,
+        node_data: &[ClipScrollNodeData],
     ) -> PrimitiveRunLocalRect {
         let mut result = PrimitiveRunLocalRect {
             local_rect_in_actual_parent_space: LayerRect::zero(),
             local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
         for run in runs {
             // TODO(gw): Perhaps we can restructure this to not need to create
@@ -1606,27 +1844,28 @@ impl PrimitiveStore {
                     clip_scroll_tree,
                     pipelines,
                     perform_culling,
                     parent_tasks,
                     scene_properties,
                     profile_counters,
                     pic_index,
                     screen_rect,
+                    node_data,
                 ) {
                     profile_counters.visible_primitives.inc();
 
                     if let Some(ref matrix) = original_relative_transform {
-                        let bounds = get_local_bounding_rect(&prim_local_rect, matrix);
+                        let bounds = matrix.transform_rect(&prim_local_rect);
                         result.local_rect_in_original_parent_space =
                             result.local_rect_in_original_parent_space.union(&bounds);
                     }
 
                     if let Some(ref matrix) = parent_relative_transform {
-                        let bounds = get_local_bounding_rect(&prim_local_rect, matrix);
+                        let bounds = matrix.transform_rect(&prim_local_rect);
                         result.local_rect_in_actual_parent_space =
                             result.local_rect_in_actual_parent_space.union(&bounds);
                     }
                 }
             }
         }
 
         result
@@ -1653,33 +1892,24 @@ impl InsideTest<ComplexClipRegion> for C
             clip.radii.top_right.height >= self.radii.top_right.height - delta_top &&
             clip.radii.bottom_left.width >= self.radii.bottom_left.width - delta_left &&
             clip.radii.bottom_left.height >= self.radii.bottom_left.height - delta_bottom &&
             clip.radii.bottom_right.width >= self.radii.bottom_right.width - delta_right &&
             clip.radii.bottom_right.height >= self.radii.bottom_right.height - delta_bottom
     }
 }
 
-fn get_local_bounding_rect(
+fn create_nine_patch(
     local_rect: &LayerRect,
-    matrix: &LayerTransform
-) -> LayerRect {
-    let vertices = [
-        matrix.transform_point3d(&local_rect.origin.to_3d()),
-        matrix.transform_point3d(&local_rect.bottom_left().to_3d()),
-        matrix.transform_point3d(&local_rect.bottom_right().to_3d()),
-        matrix.transform_point3d(&local_rect.top_right().to_3d()),
-    ];
+    local_clip_rect: &LayerRect,
+    radii: &BorderRadius
+) -> Option<Box<BrushSegmentDescriptor>> {
+    extract_inner_rect_safe(local_clip_rect, radii).map(|inner| {
+        let mut desc = BrushSegmentDescriptor::new(
+            local_rect,
+            &inner,
+            None,
+        );
+        desc.can_optimize_clip_mask = true;
 
-    let mut x0 = vertices[0].x;
-    let mut y0 = vertices[0].y;
-    let mut x1 = vertices[0].x;
-    let mut y1 = vertices[0].y;
-
-    for v in &vertices[1..] {
-        x0 = x0.min(v.x);
-        y0 = y0.min(v.y);
-        x1 = x1.max(v.x);
-        y1 = y1.max(v.y);
-    }
-
-    LayerRect::new(LayerPoint::new(x0, y0), LayerSize::new(x1 - x0, y1 - y0))
+        Box::new(desc)
+    })
 }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.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 api::{ClipId, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
 use api::{LayerPoint, LayerRect, PremultipliedColorF};
 use box_shadow::BoxShadowCacheKey;
-use clip::{ClipSource, ClipSourcesWeakHandle, ClipStore};
+use clip::{ClipSourcesWeakHandle};
 use clip_scroll_tree::CoordinateSystemId;
 use euclid::TypedSize2D;
 use gpu_types::{ClipScrollNodeIndex};
 use picture::RasterizationSpace;
 use prim_store::{PrimitiveIndex};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use std::{cmp, ops, usize, f32, i32};
@@ -159,88 +159,27 @@ pub enum RenderTaskKey {
 }
 
 #[derive(Debug)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
-#[derive(Debug, Copy, Clone)]
-#[repr(C)]
-pub enum MaskSegment {
-    // This must match the SEGMENT_ values in clip_shared.glsl!
-    All = 0,
-    TopLeftCorner,
-    TopRightCorner,
-    BottomLeftCorner,
-    BottomRightCorner,
-}
-
-#[derive(Debug, Copy, Clone)]
-#[repr(C)]
-pub enum MaskGeometryKind {
-    Default, // Draw the entire rect
-    CornersOnly, // Draw the corners (simple axis aligned mask)
-             // TODO(gw): Add more types here (e.g. 4 rectangles outside the inner rect)
-}
-
 #[derive(Debug, Clone)]
 pub struct ClipWorkItem {
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
-impl ClipWorkItem {
-    fn get_geometry_kind(
-        &self,
-        clip_store: &ClipStore,
-        prim_coordinate_system_id: CoordinateSystemId
-    ) -> MaskGeometryKind {
-        let clips = clip_store
-            .get_opt(&self.clip_sources)
-            .expect("bug: clip handle should be valid")
-            .clips();
-        let mut rounded_rect_count = 0;
-
-        for &(ref clip, _) in clips {
-            match *clip {
-                ClipSource::Rectangle(..) => {
-                    if !self.has_compatible_coordinate_system(prim_coordinate_system_id) {
-                        return MaskGeometryKind::Default;
-                    }
-                },
-                ClipSource::RoundedRectangle(..) => {
-                    rounded_rect_count += 1;
-                }
-                ClipSource::Image(..) | ClipSource::BorderCorner(..) => {
-                    return MaskGeometryKind::Default;
-                }
-            }
-        }
-
-        if rounded_rect_count == 1 {
-            MaskGeometryKind::CornersOnly
-        } else {
-            MaskGeometryKind::Default
-        }
-    }
-
-    fn has_compatible_coordinate_system(&self, other_id: CoordinateSystemId) -> bool {
-        self.coordinate_system_id == other_id
-    }
-}
-
 #[derive(Debug)]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
-    inner_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
-    pub geometry_kind: MaskGeometryKind,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
     pub content_origin: LayerPoint,
@@ -348,48 +287,30 @@ impl RenderTask {
             kind: RenderTaskKind::Readback(screen_rect),
             clear_mode: ClearMode::Transparent,
         }
     }
 
     pub fn new_mask(
         key: Option<ClipId>,
         outer_rect: DeviceIntRect,
-        inner_rect: DeviceIntRect,
         clips: Vec<ClipWorkItem>,
-        clip_store: &ClipStore,
-        is_axis_aligned: bool,
         prim_coordinate_system_id: CoordinateSystemId,
-    ) -> Option<RenderTask> {
-        // TODO(gw): This optimization is very conservative for now.
-        //           For now, only draw optimized geometry if it is
-        //           a single aligned rect mask with rounded corners.
-        //           In the future, we'll expand this to handle the
-        //           more complex types of clip mask geometry.
-        let geometry_kind = if is_axis_aligned &&
-            clips.len() == 1 &&
-            inner_rect.size != DeviceIntSize::zero() {
-            clips[0].get_geometry_kind(clip_store, prim_coordinate_system_id)
-        } else {
-            MaskGeometryKind::Default
-        };
-
-        Some(RenderTask {
+    ) -> RenderTask {
+        RenderTask {
             cache_key: key.map(RenderTaskKey::CacheMask),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
-                inner_rect: inner_rect,
                 clips,
-                geometry_kind,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
-        })
+        }
     }
 
     // Construct a render task to apply a blur to a primitive.
     // The render task chain that is constructed looks like:
     //
     //    PrimitiveCacheTask: Draw the primitives.
     //           ^
     //           |
@@ -524,22 +445,17 @@ impl RenderTask {
             }
             RenderTaskKind::CacheMask(ref task) => {
                 (
                     [
                         task.actual_rect.origin.x as f32,
                         task.actual_rect.origin.y as f32,
                         0.0,
                     ],
-                    [
-                        task.inner_rect.origin.x as f32,
-                        task.inner_rect.origin.y as f32,
-                        (task.inner_rect.origin.x + task.inner_rect.size.width) as f32,
-                        (task.inner_rect.origin.y + task.inner_rect.size.height) as f32,
-                    ],
+                    [0.0; 4],
                 )
             }
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 (
                     [
                         task.blur_std_deviation,
                         task.scale_factor,
@@ -668,17 +584,16 @@ impl RenderTask {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
                 pt.add_item(format!("kind: {:?}", task.target_kind));
                 pt.add_item(format!("space: {:?}", task.rasterization_kind));
             }
             RenderTaskKind::CacheMask(ref task) => {
                 pt.new_level(format!("CacheMask with {} clips", task.clips.len()));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
-                pt.add_item(format!("geometry: {:?}", task.geometry_kind));
             }
             RenderTaskKind::VerticalBlur(ref task) => {
                 pt.new_level("VerticalBlur".to_owned());
                 task.print_with(pt);
             }
             RenderTaskKind::HorizontalBlur(ref task) => {
                 pt.new_level("HorizontalBlur".to_owned());
                 task.print_with(pt);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -20,17 +20,17 @@ use api::{YuvColorSpace, YuvFormat};
 use api::ApiMsg;
 use api::DebugCommand;
 #[cfg(not(feature = "debugger"))]
 use api::channel::MsgSender;
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
 use debug_server::{self, DebugServer};
-use device::{DepthFunction, Device, FrameId, Program, Texture,
+use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture,
              VertexDescriptor, PBO};
 use device::{get_gl_format_bgra, ExternalTexture, FBOId, TextureSlot, VertexAttribute,
              VertexAttributeKind};
 use device::{FileWatcherHandler, ShaderError, TextureFilter, TextureTarget,
              VertexUsageHint, VAO};
 use device::ProgramCache;
 use euclid::{rect, ScaleFactor, Transform3D};
 use frame_builder::FrameBuilderConfig;
@@ -67,16 +67,20 @@ use thread_profiler::{register_thread_wi
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{RenderPass, RenderPassKind, RenderTargetKind, RenderTargetList};
 use tiling::{BatchKey, BatchKind, BrushBatchKind, Frame, RenderTarget, ScalingInfo, TransformBatchKind};
 use time::precise_time_ns;
 use util::TransformedRectKind;
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
+const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
+    label: "B_Solid",
+    color: debug_colors::RED,
+};
 const GPU_TAG_BRUSH_MASK: GpuProfileTag = GpuProfileTag {
     label: "B_Mask",
     color: debug_colors::BLACK,
 };
 const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "B_Image",
     color: debug_colors::SILVER,
 };
@@ -95,20 +99,16 @@ const GPU_TAG_CACHE_LINE: GpuProfileTag 
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     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,
 };
@@ -173,17 +173,16 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuPr
     label: "Transparent Pass",
     color: debug_colors::BLACK,
 };
 
 impl TransformBatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
-            TransformBatchKind::Rectangle(..) => "Rectangle",
             TransformBatchKind::TextRun(..) => "TextRun",
             TransformBatchKind::Image(image_buffer_kind, ..) => match image_buffer_kind {
                 ImageBufferKind::Texture2D => "Image (2D)",
                 ImageBufferKind::TextureRect => "Image (Rect)",
                 ImageBufferKind::TextureExternal => "Image (External)",
                 ImageBufferKind::Texture2DArray => "Image (Array)",
             },
             TransformBatchKind::YuvImage(..) => "YuvImage",
@@ -193,17 +192,16 @@ impl TransformBatchKind {
             TransformBatchKind::BorderCorner => "BorderCorner",
             TransformBatchKind::BorderEdge => "BorderEdge",
             TransformBatchKind::Line => "Line",
         }
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
-            TransformBatchKind::Rectangle(_) => GPU_TAG_PRIM_RECT,
             TransformBatchKind::Line => GPU_TAG_PRIM_LINE,
             TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN,
             TransformBatchKind::Image(..) => GPU_TAG_PRIM_IMAGE,
             TransformBatchKind::YuvImage(..) => GPU_TAG_PRIM_YUV_IMAGE,
             TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER,
             TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE,
             TransformBatchKind::AlignedGradient => GPU_TAG_PRIM_GRADIENT,
             TransformBatchKind::AngleGradient => GPU_TAG_PRIM_ANGLE_GRADIENT,
@@ -215,28 +213,38 @@ impl TransformBatchKind {
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             BatchKind::Composite { .. } => "Composite",
             BatchKind::HardwareComposite => "HardwareComposite",
             BatchKind::SplitComposite => "SplitComposite",
             BatchKind::Blend => "Blend",
-            BatchKind::Brush(BrushBatchKind::Image(..)) => "Brush (Image)",
+            BatchKind::Brush(kind) => {
+                match kind {
+                    BrushBatchKind::Image(..) => "Brush (Image)",
+                    BrushBatchKind::Solid => "Brush (Solid)",
+                }
+            }
             BatchKind::Transformable(_, batch_kind) => batch_kind.debug_name(),
         }
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
             BatchKind::Composite { .. } => GPU_TAG_PRIM_COMPOSITE,
             BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
             BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
             BatchKind::Blend => GPU_TAG_PRIM_BLEND,
-            BatchKind::Brush(BrushBatchKind::Image(_)) => GPU_TAG_BRUSH_IMAGE,
+            BatchKind::Brush(kind) => {
+                match kind {
+                    BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
+                    BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
+                }
+            }
             BatchKind::Transformable(_, batch_kind) => batch_kind.gpu_sampler_tag(),
         }
     }
 }
 
 bitflags! {
     #[derive(Default)]
     pub struct DebugFlags: u32 {
@@ -790,59 +798,48 @@ impl CacheTexture {
         let mut updated_blocks = 0;
         for update in &updates.updates {
             updated_blocks += self.apply_patch(update, &updates.blocks);
         }
         updated_blocks
     }
 
     fn flush(&mut self, device: &mut Device) -> usize {
-        // Bind a PBO to do the texture upload.
-        // Updating the texture via PBO avoids CPU-side driver stalls.
-        device.bind_pbo(Some(&self.pbo));
-
-        let mut rows_dirty = 0;
+        let rows_dirty = self.rows
+            .iter()
+            .filter(|row| row.is_dirty)
+            .count();
+        if rows_dirty == 0 {
+            return 0
+        }
+
+        let mut uploader = device.upload_texture(
+            &self.texture,
+            &self.pbo,
+            rows_dirty * MAX_VERTEX_TEXTURE_WIDTH,
+        );
 
         for (row_index, row) in self.rows.iter_mut().enumerate() {
             if !row.is_dirty {
                 continue;
             }
 
-            // Get the data for this row and push to the PBO.
             let block_index = row_index * MAX_VERTEX_TEXTURE_WIDTH;
             let cpu_blocks =
                 &self.cpu_blocks[block_index .. (block_index + MAX_VERTEX_TEXTURE_WIDTH)];
-            device.update_pbo_data(cpu_blocks);
-
-            // Insert a command to copy the PBO data to the right place in
-            // the GPU-side cache texture.
-            device.update_texture_from_pbo(
-                &self.texture,
-                0,
-                row_index as u32,
-                MAX_VERTEX_TEXTURE_WIDTH as u32,
-                1,
-                0,
-                None,
-                0,
+            let rect = DeviceUintRect::new(
+                DeviceUintPoint::new(0, row_index as u32),
+                DeviceUintSize::new(MAX_VERTEX_TEXTURE_WIDTH as u32, 1),
             );
 
-            // Orphan the PBO. This is the recommended way to hint to the
-            // driver to detach the underlying storage from this PBO id.
-            // Keeping the size the same gives the driver a hint for future
-            // use of this PBO.
-            device.orphan_pbo(mem::size_of::<GpuBlockData>() * MAX_VERTEX_TEXTURE_WIDTH);
-
-            rows_dirty += 1;
+            uploader.upload(rect, 0, None, cpu_blocks);
+
             row.is_dirty = false;
         }
 
-        // Ensure that other texture updates won't read from this PBO.
-        device.bind_pbo(None);
-
         rows_dirty
     }
 }
 
 struct VertexDataTexture {
     texture: Texture,
     pbo: PBO,
 }
@@ -890,34 +887,32 @@ impl VertexDataTexture {
                 ImageFormat::RGBAF32,
                 TextureFilter::Nearest,
                 None,
                 1,
                 None,
             );
         }
 
-        // Bind a PBO to do the texture upload.
-        // Updating the texture via PBO avoids CPU-side driver stalls.
-        device.bind_pbo(Some(&self.pbo));
-        device.update_pbo_data(data);
-        device.update_texture_from_pbo(&self.texture, 0, 0, width, needed_height, 0, None, 0);
-
-        // Ensure that other texture updates won't read from this PBO.
-        device.bind_pbo(None);
+        let rect = DeviceUintRect::new(
+            DeviceUintPoint::zero(),
+            DeviceUintSize::new(width, needed_height),
+        );
+        device
+            .upload_texture(&self.texture, &self.pbo, 0)
+            .upload(rect, 0, None, data);
     }
 
     fn deinit(self, device: &mut Device) {
         device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 }
 
 const TRANSFORM_FEATURE: &str = "TRANSFORM";
-const CLIP_FEATURE: &str = "CLIP";
 const ALPHA_FEATURE: &str = "ALPHA_PASS";
 
 enum ShaderKind {
     Primitive,
     Cache(VertexArrayKind),
     ClipCache,
     Brush,
     Text,
@@ -1350,33 +1345,32 @@ pub struct Renderer {
     cs_blur_a8: LazilyCompiledShader,
     cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
     brush_mask_corner: LazilyCompiledShader,
     brush_mask_rounded_rect: LazilyCompiledShader,
     brush_image_rgba8: BrushShader,
     brush_image_a8: BrushShader,
+    brush_solid: BrushShader,
 
     /// 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
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
-    ps_rectangle: PrimitiveShader,
-    ps_rectangle_clip: PrimitiveShader,
     ps_text_run: TextShader,
     ps_text_run_subpx_bg_pass1: TextShader,
     ps_image: Vec<Option<PrimitiveShader>>,
     ps_yuv_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
@@ -1493,16 +1487,17 @@ impl Renderer {
         let file_watch_handler = FileWatcher {
             result_tx: result_tx.clone(),
             notifier: notifier.clone(),
         };
 
         let mut device = Device::new(
             gl,
             options.resource_override_path.clone(),
+            options.upload_method,
             Box::new(file_watch_handler),
             options.cached_programs,
         );
 
         let device_max_size = device.max_texture_size();
         // 512 is the minimum that the texture cache can work with.
         // Broken GL contexts can return a max texture size of zero (See #1260). Better to
         // gracefully fail now than panic as soon as a texture is allocated.
@@ -1553,16 +1548,23 @@ impl Renderer {
         let brush_mask_rounded_rect = try!{
             LazilyCompiledShader::new(ShaderKind::Brush,
                                       "brush_mask_rounded_rect",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let brush_solid = try!{
+            BrushShader::new("brush_solid",
+                             &mut device,
+                             &[],
+                             options.precache_shaders)
+        };
+
         let brush_image_a8 = try!{
             BrushShader::new("brush_image",
                              &mut device,
                              &["ALPHA_TARGET"],
                              options.precache_shaders)
         };
 
         let brush_image_rgba8 = try!{
@@ -1607,30 +1609,16 @@ impl Renderer {
         let cs_clip_border = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_border",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
-        let ps_rectangle = try!{
-            PrimitiveShader::new("ps_rectangle",
-                                 &mut device,
-                                 &[],
-                                 options.precache_shaders)
-        };
-
-        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!{
@@ -2006,21 +1994,20 @@ impl Renderer {
             cs_text_run,
             cs_line,
             cs_blur_a8,
             cs_blur_rgba8,
             brush_mask_corner,
             brush_mask_rounded_rect,
             brush_image_rgba8,
             brush_image_a8,
+            brush_solid,
             cs_clip_rectangle,
             cs_clip_border,
             cs_clip_image,
-            ps_rectangle,
-            ps_rectangle_clip,
             ps_text_run,
             ps_text_run_subpx_bg_pass1,
             ps_image,
             ps_yuv_image,
             ps_border_corner,
             ps_border_edge,
             ps_gradient,
             ps_angle_gradient,
@@ -2645,62 +2632,55 @@ impl Renderer {
                     TextureUpdateOp::Update {
                         rect,
                         source,
                         stride,
                         layer_index,
                         offset,
                     } => {
                         let texture = &self.texture_resolver.cache_texture_map[update.id.0];
-
-                        // Bind a PBO to do the texture upload.
-                        // Updating the texture via PBO avoids CPU-side driver stalls.
-                        self.device.bind_pbo(Some(&self.texture_cache_upload_pbo));
+                        let mut uploader = self.device.upload_texture(
+                            texture,
+                            &self.texture_cache_upload_pbo,
+                            0,
+                        );
 
                         match source {
                             TextureUpdateSource::Bytes { data } => {
-                                self.device.update_pbo_data(&data[offset as usize ..]);
+                                uploader.upload(
+                                    rect, layer_index, stride,
+                                    &data[offset as usize ..],
+                                );
                             }
                             TextureUpdateSource::External { id, channel_index } => {
                                 let handler = self.external_image_handler
                                     .as_mut()
                                     .expect("Found external image, but no handler set!");
                                 match handler.lock(id, channel_index).source {
                                     ExternalImageSource::RawData(data) => {
-                                        self.device.update_pbo_data(&data[offset as usize ..]);
+                                        uploader.upload(
+                                            rect, layer_index, stride,
+                                            &data[offset as usize ..],
+                                        );
                                     }
                                     ExternalImageSource::Invalid => {
                                         // Create a local buffer to fill the pbo.
                                         let bpp = texture.get_bpp();
                                         let width = stride.unwrap_or(rect.size.width * bpp);
                                         let total_size = width * rect.size.height;
                                         // WR haven't support RGBAF32 format in texture_cache, so
                                         // we use u8 type here.
                                         let dummy_data: Vec<u8> = vec![255; total_size as usize];
-                                        self.device.update_pbo_data(&dummy_data);
+                                        uploader.upload(rect, layer_index, stride, &dummy_data);
                                     }
                                     _ => panic!("No external buffer found"),
                                 };
                                 handler.unlock(id, channel_index);
                             }
                         }
-
-                        self.device.update_texture_from_pbo(
-                            texture,
-                            rect.origin.x,
-                            rect.origin.y,
-                            rect.size.width,
-                            rect.size.height,
-                            layer_index,
-                            stride,
-                            0,
-                        );
-
-                        // Ensure that other texture updates won't read from this PBO.
-                        self.device.bind_pbo(None);
                     }
                     TextureUpdateOp::Free => {
                         let texture = &mut self.texture_resolver.cache_texture_map[update.id.0];
                         self.device.free_texture_storage(texture);
                     }
                 }
             }
         }
@@ -2782,62 +2762,41 @@ impl Renderer {
                     &mut self.renderer_errors,
                 );
             }
             BatchKind::Blend => {
                 self.ps_blend.bind(&mut self.device, projection, 0, &mut self.renderer_errors);
             }
             BatchKind::Brush(brush_kind) => {
                 match brush_kind {
+                    BrushBatchKind::Solid => {
+                        self.brush_solid.bind(
+                            &mut self.device,
+                            key.blend_mode,
+                            projection,
+                            0,
+                            &mut self.renderer_errors,
+                        );
+                    }
                     BrushBatchKind::Image(target_kind) => {
                         let shader = match target_kind {
                             RenderTargetKind::Alpha => &mut self.brush_image_a8,
                             RenderTargetKind::Color => &mut self.brush_image_rgba8,
                         };
                         shader.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
                     }
                 }
             }
             BatchKind::Transformable(transform_kind, batch_kind) => match batch_kind {
-                TransformBatchKind::Rectangle(needs_clipping) => {
-                    debug_assert!(
-                        !needs_clipping || match key.blend_mode {
-                            BlendMode::PremultipliedAlpha |
-                            BlendMode::PremultipliedDestOut |
-                            BlendMode::SubpixelConstantTextColor(..) |
-                            BlendMode::SubpixelVariableTextColor |
-                            BlendMode::SubpixelWithBgColor => true,
-                            BlendMode::None => false,
-                        }
-                    );
-
-                    if needs_clipping {
-                        self.ps_rectangle_clip.bind(
-                            &mut self.device,
-                            transform_kind,
-                            projection,
-                            0,
-                            &mut self.renderer_errors,
-                        );
-                    } else {
-                        self.ps_rectangle.bind(
-                            &mut self.device,
-                            transform_kind,
-                            projection,
-                            0,
-                            &mut self.renderer_errors,
-                        );
-                    }
-                }
                 TransformBatchKind::Line => {
                     self.ps_line.bind(
                         &mut self.device,
                         transform_kind,
                         projection,
                         0,
                         &mut self.renderer_errors,
                     );
@@ -3771,17 +3730,17 @@ impl Renderer {
                 self.prepare_target_list(alpha, true);
                 self.prepare_target_list(color, true);
             }
         }
     }
 
     fn bind_frame_data(&mut self, frame: &mut Frame) {
         let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_DATA);
-        self.device.device_pixel_ratio = frame.device_pixel_ratio;
+        self.device.set_device_pixel_ratio(frame.device_pixel_ratio);
 
         // Some of the textures are already assigned by `prepare_frame`.
         // Now re-allocate the space for the rest of the target textures.
         for pass in &mut frame.passes {
             if let RenderPassKind::OffScreen { ref mut alpha, ref mut color } = pass.kind {
                 self.prepare_target_list(alpha, false);
                 self.prepare_target_list(color, false);
             }
@@ -4174,21 +4133,20 @@ impl Renderer {
         self.cs_text_run.deinit(&mut self.device);
         self.cs_line.deinit(&mut self.device);
         self.cs_blur_a8.deinit(&mut self.device);
         self.cs_blur_rgba8.deinit(&mut self.device);
         self.brush_mask_rounded_rect.deinit(&mut self.device);
         self.brush_mask_corner.deinit(&mut self.device);
         self.brush_image_rgba8.deinit(&mut self.device);
         self.brush_image_a8.deinit(&mut self.device);
+        self.brush_solid.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
-        self.ps_rectangle.deinit(&mut self.device);
-        self.ps_rectangle_clip.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_subpx_bg_pass1.deinit(&mut self.device);
         for shader in self.ps_image {<