merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 10 Sep 2017 10:09:56 +0200
changeset 429449 7677154bc3e03ab6e2fa5b1901ada5280509b07d
parent 429448 0821986e4f3bef817c0e56ae802421a3b191d8f5 (current diff)
parent 429438 00baeed193d4f276694912f0298336880280837d (diff)
child 429450 c60f2681cf9c5960f15dc5fcd0d34681c725e959
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.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 mozilla-inbound. r=merge a=merge
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -474,33 +474,27 @@ var PlacesCommandHook = {
 
     // Revert the contents of the location bar
     gURLBar.handleRevert();
 
     // If it was not requested to open directly in "edit" mode, we are done.
     if (!aShowEditUI)
       return;
 
-    // Try to dock the panel to:
-    // 1. the bookmarks menu button
-    // 2. the identity icon
-    // 3. the content area
-    if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      await StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
-                                        "bottomcenter topright", isNewBookmark, uri);
+    let anchor = BookmarkingUI.anchor;
+    if (anchor) {
+      await StarUI.showEditBookmarkPopup(itemId, anchor,
+                                         "bottomcenter topright", isNewBookmark,
+                                         uri);
       return;
     }
 
-    let identityIcon = document.getElementById("identity-icon");
-    if (isVisible(identityIcon)) {
-      await StarUI.showEditBookmarkPopup(itemId, identityIcon,
-                                        "bottomcenter topright", isNewBookmark, uri);
-    } else {
-      await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark, uri);
-    }
+    // Fall back to showing the panel over the content area.
+    await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap",
+                                       isNewBookmark, uri);
   },
 
   // TODO: Replace bookmarkPage code with this function once legacy
   // transactions are removed.
   async _bookmarkPagePT(aBrowser, aParentId, aShowEditUI, aUrl, aTitle) {
     // If aUrl is provided, we want to bookmark that url rather than the
     // the current page
     let url = aUrl ? new URL(aUrl) : new URL(aBrowser.currentURI.spec);
@@ -556,33 +550,26 @@ var PlacesCommandHook = {
     gURLBar.handleRevert();
 
     // If it was not requested to open directly in "edit" mode, we are done.
     if (!aShowEditUI)
       return;
 
     let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(info);
 
-    // Try to dock the panel to:
-    // 1. the bookmarks menu button
-    // 2. the identity icon
-    // 3. the content area
-    if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      await StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
-                                   "bottomcenter topright", isNewBookmark, url);
+    let anchor = BookmarkingUI.anchor;
+    if (anchor) {
+      await StarUI.showEditBookmarkPopup(node, anchor, "bottomcenter topright",
+                                         isNewBookmark, url);
       return;
     }
 
-    let identityIcon = document.getElementById("identity-icon");
-    if (isVisible(identityIcon)) {
-      await StarUI.showEditBookmarkPopup(node, identityIcon,
-                                   "bottomcenter topright", isNewBookmark, url);
-    } else {
-      await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark, url);
-    }
+    // Fall back to showing the panel over the content area.
+    await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark,
+                                       url);
   },
 
   _getPageDetails(browser) {
     return new Promise(resolve => {
       let mm = browser.messageManager;
       mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) {
         mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener);
         resolve(msg.data);
@@ -1582,31 +1569,48 @@ var LibraryUI = {
 };
 
 /**
  * Handles the bookmarks menu-button in the toolbar.
  */
 
 var BookmarkingUI = {
   STAR_ID: "star-button",
+  STAR_BOX_ID: "star-button-box",
   BOOKMARK_BUTTON_ID: "bookmarks-menu-button",
   BOOKMARK_BUTTON_SHORTCUT: "addBookmarkAsKb",
   get button() {
     delete this.button;
     let widgetGroup = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID);
     return this.button = widgetGroup.forWindow(window).node;
   },
 
   get star() {
     delete this.star;
     return this.star = document.getElementById(this.STAR_ID);
   },
 
+  get starBox() {
+    delete this.starBox;
+    return this.starBox = document.getElementById(this.STAR_BOX_ID);
+  },
+
   get anchor() {
-    return this.star;
+    // Try to anchor the panel to:
+    // 1. The bookmarks star box (using the star itself is trickier because it
+    //    can be hidden while the star animation is visible)
+    // 2. The identity icon
+    if (this.starBox && isVisible(this.starBox)) {
+      return this.starBox;
+    }
+    let identityIcon = document.getElementById("identity-icon");
+    if (identityIcon && isVisible(identityIcon)) {
+      return identityIcon;
+    }
+    return null;
   },
 
   get notifier() {
     delete this.notifier;
     return this.notifier = document.getElementById("bookmarked-notification-anchor");
   },
 
   get dropmarkerNotifier() {
--- a/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
+++ b/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
@@ -225,13 +225,52 @@ add_task(async function onSearch() {
   SitePermissions.set(u, "desktop-notification", SitePermissions.ALLOW);
 
   Assert.equal(doc.getElementsByAttribute("origin", "http://www.test.com")[0], null);
   Assert.equal(doc.getElementsByAttribute("origin", "http://www.example.com")[0],
                richlistbox.getItemAtIndex(0));
 
   SitePermissions.remove(URI, "desktop-notification");
   SitePermissions.remove(u, "desktop-notification");
+
+  doc.getElementById("cancel").click();
+});
+
+add_task(async function onPermissionsSort() {
+  SitePermissions.set(URI, "desktop-notification", SitePermissions.ALLOW);
+  let u = Services.io.newURI("http://www.test.com");
+  SitePermissions.set(u, "desktop-notification", SitePermissions.BLOCK);
+
+  await openPermissionsDialog();
+  let doc = sitePermissionsDialog.document;
+  let richlistbox = doc.getElementById("permissionsBox");
+
+  // Test default arrangement(Allow followed by Block).
+  Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), "http://www.example.com");
+  Assert.equal(richlistbox.getItemAtIndex(1).getAttribute("origin"), "http://www.test.com");
+
+  doc.getElementById("statusCol").click();
+
+  // Test the rearrangement(Block followed by Allow).
+  Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), "http://www.test.com");
+  Assert.equal(richlistbox.getItemAtIndex(1).getAttribute("origin"), "http://www.example.com");
+
+  doc.getElementById("siteCol").click();
+
+  // Test the rearrangement(Website names arranged in alphabhetical order).
+  Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), "http://www.example.com");
+  Assert.equal(richlistbox.getItemAtIndex(1).getAttribute("origin"), "http://www.test.com");
+
+  doc.getElementById("siteCol").click();
+
+  // Test the rearrangement(Website names arranged in reverse alphabhetical order).
+  Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), "http://www.test.com");
+  Assert.equal(richlistbox.getItemAtIndex(1).getAttribute("origin"), "http://www.example.com");
+
+  SitePermissions.remove(URI, "desktop-notification");
+  SitePermissions.remove(u, "desktop-notification");
+
+  doc.getElementById("cancel").click();
 });
 
 add_task(async function removeTab() {
   gBrowser.removeCurrentTab();
 });
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -47,16 +47,17 @@ var gSitePermissionsManager = {
     let permissionsText = document.getElementById("permissionsText");
     while (permissionsText.hasChildNodes())
       permissionsText.firstChild.remove();
     permissionsText.appendChild(document.createTextNode(params.introText));
 
     document.title = params.windowTitle;
 
     this._loadPermissions();
+    this.buildPermissionsList();
 
     this._searchBox.focus();
   },
 
   uninit() {
     if (this._isObserving) {
       Services.obs.removeObserver(this, "perm-changed");
       this._isObserving = false;
@@ -70,24 +71,23 @@ var gSitePermissionsManager = {
     let permission = subject.QueryInterface(Components.interfaces.nsIPermission);
 
     // Ignore unrelated permission types.
     if (permission.type !== this._type)
       return;
 
     if (data == "added") {
       this._addPermissionToList(permission);
-      if (this._searchBox.value != "") {
-        this.filterPermissionsList();
-      }
+      this.buildPermissionsList();
     } else if (data == "changed") {
       let p = this._permissions.get(permission.principal.origin);
       p.capability = permission.capability;
       p.capabilityString = this._getCapabilityString(permission.capability);
       this._handleCapabilityChange(p);
+      this.buildPermissionsList();
     } else if (data == "deleted") {
       this._removePermissionFromList(permission.principal.origin);
     }
   },
 
   _handleCapabilityChange(perm) {
     let permissionlistitem = document.getElementsByAttribute("origin", perm.origin)[0];
     let menulist = permissionlistitem.getElementsByTagName("menulist")[0];
@@ -113,35 +113,31 @@ var gSitePermissionsManager = {
 
   _addPermissionToList(perm) {
     if (perm.type !== this._type)
       return;
     let capabilityString = this._getCapabilityString(perm.capability);
     let p = new Permission(perm.principal, perm.type, perm.capability,
                            capabilityString);
     this._permissions.set(p.origin, p);
-    this._createPermissionListItem(p);
   },
 
   _removePermissionFromList(origin) {
     this._permissions.delete(origin);
     let permissionlistitem = document.getElementsByAttribute("origin", origin)[0];
     this._list.removeItemAt(this._list.getIndexOfItem(permissionlistitem));
   },
 
   _loadPermissions() {
     // load permissions into a table.
     let enumerator = Services.perms.enumerator;
     while (enumerator.hasMoreElements()) {
       let nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
       this._addPermissionToList(nextPermission);
     }
-
-    // disable "remove all" button if there are none
-    this._setRemoveButtonState();
   },
 
   _createPermissionListItem(permission) {
     let richlistitem = document.createElement("richlistitem");
     richlistitem.setAttribute("origin", permission.origin);
     let row = document.createElement("hbox");
     row.setAttribute("flex", "1");
 
@@ -255,27 +251,75 @@ var gSitePermissionsManager = {
 
     for (let p of this._permissionsToDelete.values()) {
       let uri = Services.io.newURI(p.origin);
       SitePermissions.remove(uri, p.type);
     }
     window.close();
   },
 
-  filterPermissionsList() {
+  buildPermissionsList(sortCol) {
     // Clear old entries.
     let oldItems = this._list.querySelectorAll("richlistitem");
     for (let item of oldItems) {
       item.remove();
     }
 
+    // Sort permissions.
+    let sortedPermissions = this._sortPermissions(sortCol);
+
     let keyword = this._searchBox.value.toLowerCase().trim();
-    let permissions = this._permissions;
-    for (let [origin, permission] of permissions) {
-      if (keyword && !origin.includes(keyword)) {
+    for (let permission of sortedPermissions) {
+      if (keyword && !permission.origin.includes(keyword)) {
         continue;
       }
 
       this._createPermissionListItem(permission);
     }
+
     this._setRemoveButtonState();
   },
+
+  _sortPermissions(column) {
+    let permissions = Array.from(this._permissions.values());
+    let sortDirection;
+
+    if (!column) {
+      column = document.querySelector("treecol[data-isCurrentSortCol=true]");
+      sortDirection = column.getAttribute("data-last-sortDirection") || "ascending";
+    } else {
+      sortDirection = column.getAttribute("data-last-sortDirection");
+      sortDirection = sortDirection === "ascending" ? "descending" : "ascending";
+    }
+
+    let sortFunc = null;
+    switch (column.id) {
+      case "siteCol":
+        sortFunc = (a, b) => {
+          return a.origin.localeCompare(b.origin);
+        };
+        break;
+
+      case "statusCol":
+        sortFunc = (a, b) => {
+          return a.capabilityString.localeCompare(b.capabilityString);
+        };
+        break;
+    }
+
+    if (sortDirection === "descending") {
+      permissions.sort((a, b) => sortFunc(b, a));
+    } else {
+      permissions.sort(sortFunc);
+    }
+
+    let cols = this._list.querySelectorAll("treecol");
+    cols.forEach(c => {
+      c.removeAttribute("data-isCurrentSortCol");
+      c.removeAttribute("sortDirection");
+    });
+    column.setAttribute("data-isCurrentSortCol", "true");
+    column.setAttribute("sortDirection", sortDirection);
+    column.setAttribute("data-last-sortDirection", sortDirection);
+
+    return permissions;
+  },
 };
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -28,29 +28,31 @@
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
   <vbox class="contentPane largeDialogContainer" flex="1">
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
     <hbox align="start">
       <textbox id="searchBox" flex="1" placeholder="&searchbox.placeholder;"
-               type="search" oncommand="gSitePermissionsManager.filterPermissionsList();"/>
+               type="search" oncommand="gSitePermissionsManager.buildPermissionsList();"/>
     </hbox>
     <separator class="thin"/>
     <richlistbox id="permissionsBox" selected="false"
                  hidecolumnpicker="true" flex="1"
                  onkeypress="gSitePermissionsManager.onPermissionKeyPress(event);"
                  onselect="gSitePermissionsManager.onPermissionSelect();">
       <listheader>
         <treecol id="siteCol" label="&treehead.sitename2.label;" flex="3"
-                 data-field-name="origin" persist="width" width="50"/>
+                 persist="width" width="50"
+                 onclick="gSitePermissionsManager.buildPermissionsList(event.target)"/>
         <splitter class="tree-splitter"/>
         <treecol id="statusCol" label="&treehead.status.label;" flex="1"
-                 data-field-name="capability" persist="width" width="50"/>
+                 persist="width" width="50" data-isCurrentSortCol="true"
+                 onclick="gSitePermissionsManager.buildPermissionsList(event.target);"/>
       </listheader>
     </richlistbox>
   </vbox>
   <vbox>
     <hbox class="actionButtons" align="left" flex="1">
       <button id="removePermission" disabled="true"
               accesskey="&removepermission2.accesskey;"
               icon="remove" label="&removepermission2.label;"
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -404,17 +404,17 @@ this.FormAutofillHeuristics = {
    *        the elements in this form to be predicted the field info.
    * @param {boolean} allowDuplicates
    *        true to remain any duplicated field details otherwise to remove the
    *        duplicated ones.
    * @returns {Array<Object>}
    *        all field details in the form.
    */
   getFormInfo(form, allowDuplicates = false) {
-    if (form.autocomplete == "off" || form.elements.length <= 0) {
+    if (form.elements.length <= 0) {
       return [];
     }
 
     let fieldScanner = new FieldScanner(form.elements);
     while (!fieldScanner.parsingFinished) {
       let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
       let parsedAddressFields = this._parseAddressFields(fieldScanner);
 
@@ -437,39 +437,49 @@ this.FormAutofillHeuristics = {
   getInfo(element) {
     if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
       return null;
     }
 
     let info = element.getAutocompleteInfo();
     // An input[autocomplete="on"] will not be early return here since it stll
     // needs to find the field name.
-    if (info && info.fieldName && info.fieldName != "on") {
+    if (info && info.fieldName && info.fieldName != "on" && info.fieldName != "off") {
       info._reason = "autocomplete";
       return info;
     }
 
     if (!this._prefEnabled) {
       return null;
     }
 
+    let isAutoCompleteOff = element.autocomplete == "off" ||
+      (element.form && element.form.autocomplete == "off");
+
     // "email" type of input is accurate for heuristics to determine its Email
     // field or not. However, "tel" type is used for ZIP code for some web site
     // (e.g. HomeDepot, BestBuy), so "tel" type should be not used for "tel"
     // prediction.
-    if (element.type == "email") {
+    if (element.type == "email" && !isAutoCompleteOff) {
       return {
         fieldName: "email",
         section: "",
         addressType: "",
         contactType: "",
       };
     }
 
-    let regexps = Object.keys(this.RULES);
+    const FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF = [
+      "cc-name",
+      "cc-number",
+      "cc-exp-month",
+      "cc-exp-year",
+      "cc-exp",
+    ];
+    let regexps = isAutoCompleteOff ? FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF : Object.keys(this.RULES);
 
     let labelStrings;
     let getElementStrings = {};
     getElementStrings[Symbol.iterator] = function* () {
       yield element.id;
       yield element.name;
       if (!labelStrings) {
         labelStrings = [];
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -162,20 +162,16 @@ this.FormAutofillUtils = {
   },
 
   autofillFieldSelector(doc) {
     return doc.querySelectorAll("input, select");
   },
 
   ALLOWED_TYPES: ["text", "email", "tel", "number"],
   isFieldEligibleForAutofill(element) {
-    if (element.autocomplete == "off") {
-      return false;
-    }
-
     let tagName = element.tagName;
     if (tagName == "INPUT") {
       // `element.type` can be recognized as `text`, if it's missing or invalid.
       if (!this.ALLOWED_TYPES.includes(element.type)) {
         return false;
       }
     } else if (tagName != "SELECT") {
       return false;
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
@@ -37,17 +37,17 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
 
         // FIXME: bug 1392932 - misdetect ZIP ext string
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 
         // FIXME: bug 1392940 - the below element can not match to "cc-exp-year" regexp directly.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
       ],
       [],
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
@@ -42,26 +42,28 @@ runHeuristicsTest([
       ],
       [],
     ],
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
       [
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 
         // FIXME: bug 1392940 - the below element can not match to "cc-exp-year" regexp directly.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"}, // ac-off
       ],
-      [],
+      [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+      ],
       [],
       [],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Macys.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Macys.js
@@ -20,24 +20,22 @@ runHeuristicsTest([
 /*
 */
       ],
     ],
   }, {
     fixturePath: "Checkout_Payment.html",
     expectedResult: [
       [
- /*
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // ac-off
+//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"}, // ac-off
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
-*/
+//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
+//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_NewEgg.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_NewEgg.js
@@ -20,38 +20,38 @@ runHeuristicsTest([
       ],
       [],
     ],
   }, {
     fixturePath: "BillingInfo.html",
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
       ],
       [],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
       ],
     ],
   }, {
     fixturePath: "Login.html",
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_QVC.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_QVC.js
@@ -8,23 +8,23 @@ runHeuristicsTest([
     expectedResult: [
       [
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
 
         // FIXME: bug 1392947 - this is a compound cc-exp field rather than the
         // separated ones below. the birthday fields are misdetected as
         // cc-exp-year and cc-exp-month.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
     ],
@@ -33,23 +33,23 @@ runHeuristicsTest([
     expectedResult: [
       [
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // select
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, ac-off
 
         // FIXME: bug 1392947 - this is a compound cc-exp field rather than the
         // separated ones below. the birthday fields are misdetected as
         // cc-exp-year and cc-exp-month.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"}, // select
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
     ],
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Staples.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Staples.js
@@ -27,25 +27,34 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
       ],
     ],
   }, {
     fixturePath: "PaymentBilling.html",
     expectedResult: [
-      [],
+      [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+
+        // FIXME: bug 1392940 - Since any credit card fields should be
+        // recognized no matter it's autocomplete="off" or not. This field
+        // "cc-exp-month" should be fixed as "cc-exp".
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
+      ],
     ],
   }, {
     fixturePath: "PaymentBilling_ac_on.html",
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
 
-        // Since this is a custom fixture with force autocomplete="on", we can
-        // fix this later even this should be detected as cc-exp.
+        // FIXME: bug 1392940 - Since any credit card fields should be
+        // recognized no matter it's autocomplete="off" or not. This field
+        // "cc-exp-month" should be fixed as "cc-exp".
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
       ],
     ],
   },
 ], "../../../fixtures/third_party/Staples/");
 
--- a/browser/extensions/formautofill/test/unit/test_isFieldEligibleForAutofill.js
+++ b/browser/extensions/formautofill/test/unit/test_isFieldEligibleForAutofill.js
@@ -26,32 +26,32 @@ const TESTCASES = [
   {
     document: `<input id="targetElement" type="radio">`,
     fieldId: "targetElement",
     expectedResult: false,
   },
   {
     document: `<input id="targetElement" type="text" autocomplete="off">`,
     fieldId: "targetElement",
-    expectedResult: false,
+    expectedResult: true,
   },
   {
     document: `<input id="targetElement">`,
     fieldId: "targetElement",
     expectedResult: true,
   },
   {
     document: `<input id="targetElement" type="unknown">`,
     fieldId: "targetElement",
     expectedResult: true,
   },
   {
     document: `<select id="targetElement" autocomplete="off"></select>`,
     fieldId: "targetElement",
-    expectedResult: false,
+    expectedResult: true,
   },
   {
     document: `<select id="targetElement"></select>`,
     fieldId: "targetElement",
     expectedResult: true,
   },
   {
     document: `<select id="targetElement" multiple></select>`,
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1172,17 +1172,17 @@ TokenStream::atomize(JSContext* cx, Char
 }
 
 #ifdef DEBUG
 static bool
 IsTokenSane(Token* tp)
 {
     // Nb: TOK_EOL should never be used in an actual Token;  it should only be
     // returned as a TokenKind from peekTokenSameLine().
-    if (tp->type < 0 || tp->type >= TOK_LIMIT || tp->type == TOK_EOL)
+    if (tp->type >= TOK_LIMIT || tp->type == TOK_EOL)
         return false;
 
     if (tp->pos.end < tp->pos.begin)
         return false;
 
     return true;
 }
 #endif
--- a/servo/README.md
+++ b/servo/README.md
@@ -110,19 +110,22 @@ pip install virtualenv
 3. Install Git for Windows (https://git-scm.com/download/win). DO allow it to add git.exe to the PATH (default
 settings for the installer are fine).
 
 4. Install Visual Studio Community 2017 (https://www.visualstudio.com/). You MUST add "Visual C++" to the
 list of installed components. It is not on by default.
 > If you encountered errors with the environment above, do the following for a workaround:
 > 1.  Download and install [Build Tools for Visual Studio 2017](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15)
 > 2.  Install `python2.7 x86-x64` and `virtualenv`
-> 3.  Since `mach.bat` cannot find the prompt, you should execute `Developer Command Prompt for VS 2017` manually in the Windows menu. ( You may need to choose the type, such as `x86_x64 Cross Tools Command Prompt for VS 2017`, manually if it cannot recognize type correctly. )
-> 4. `cd to/the/path/servo`
-> 5. `python mach build -d`
+> 3.  Run `mach.bat build -d`. 
+
+>If you have troubles with `x64 type` prompt as `mach.bat` set by default:  
+> 1. you may need to choose and launch the type manually, such as `x86_x64 Cross Tools Command Prompt for VS 2017` in the Windows menu.)
+> 2. `cd to/the/path/servo`
+> 3. `python mach build -d`
 
 #### Cross-compilation for Android
 
 Pre-installed Android tools are needed. See wiki for
 [details](https://github.com/servo/servo/wiki/Building-for-Android)
 
 ## The Rust compiler
 
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -146,18 +146,18 @@ impl Angle {
         }
     }
 }
 
 impl nsStyleImage {
     /// Set a given Servo `Image` value into this `nsStyleImage`.
     pub fn set(&mut self, image: Image) {
         match image {
-            GenericImage::Gradient(gradient) => {
-                self.set_gradient(gradient)
+            GenericImage::Gradient(boxed_gradient) => {
+                self.set_gradient(*boxed_gradient)
             },
             GenericImage::Url(ref url) => {
                 unsafe {
                     Gecko_SetLayerImageImageValue(self, url.image_value.clone().unwrap().get());
                 }
             },
             GenericImage::Rect(ref image_rect) => {
                 unsafe {
@@ -394,17 +394,17 @@ impl nsStyleImage {
                     Some(GenericImage::Url(url))
                 } else {
                     let ref rect = *self.mCropRect.mPtr;
                     match (NumberOrPercentage::from_gecko_style_coord(&rect.data_at(0)),
                            NumberOrPercentage::from_gecko_style_coord(&rect.data_at(1)),
                            NumberOrPercentage::from_gecko_style_coord(&rect.data_at(2)),
                            NumberOrPercentage::from_gecko_style_coord(&rect.data_at(3))) {
                         (Some(top), Some(right), Some(bottom), Some(left)) =>
-                            Some(GenericImage::Rect(MozImageRect { url, top, right, bottom, left } )),
+                            Some(GenericImage::Rect(Box::new(MozImageRect { url, top, right, bottom, left } ))),
                         _ => {
                             debug_assert!(false, "mCropRect could not convert to NumberOrPercentage");
                             None
                         }
                     }
                 }
             },
             nsStyleImageType::eStyleImageType_Gradient => {
@@ -423,17 +423,17 @@ impl nsStyleImage {
         use gecko_bindings::bindings::Gecko_GetURLValue;
         let url_value = Gecko_GetURLValue(self);
         let mut url = SpecifiedUrl::from_url_value_data(url_value.as_ref().unwrap())
                                     .expect("Could not convert to SpecifiedUrl");
         url.build_image_value();
         url
     }
 
-    unsafe fn get_gradient(self: &nsStyleImage) -> Gradient {
+    unsafe fn get_gradient(self: &nsStyleImage) -> Box<Gradient> {
         use gecko::values::convert_nscolor_to_rgba;
         use gecko_bindings::bindings::Gecko_GetGradientImageValue;
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_CIRCULAR, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
         use values::computed::{Length, LengthOrPercentage};
         use values::computed::image::LineDirection;
@@ -576,17 +576,17 @@ impl nsStyleImage {
             if gecko_gradient.mMozLegacySyntax {
                 CompatMode::Moz
             } else if gecko_gradient.mLegacySyntax {
                 CompatMode::WebKit
             } else {
                 CompatMode::Modern
             };
 
-        Gradient { items, repeating: gecko_gradient.mRepeating, kind, compat_mode }
+        Box::new(Gradient { items, repeating: gecko_gradient.mRepeating, kind, compat_mode })
     }
 }
 
 pub mod basic_shape {
     //! Conversions from and to CSS shape representations.
 
     use gecko::values::GeckoStyleCoordConvertible;
     use gecko_bindings::structs;
--- a/servo/components/style/properties/shorthand/background.mako.rs
+++ b/servo/components/style/properties/shorthand/background.mako.rs
@@ -32,17 +32,21 @@
         }
     }
 
     pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                -> Result<Longhands, ParseError<'i>> {
         let mut background_color = None;
 
         % for name in "image position_x position_y repeat size attachment origin clip".split():
-            let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
+            // Vec grows from 0 to 4 by default on first push().  So allocate
+            // with capacity 1, so in the common case of only one item we don't
+            // way overallocate.  Note that we always push at least one item if
+            // parsing succeeds.
+            let mut background_${name} = background_${name}::SpecifiedValue(Vec::with_capacity(1));
         % endfor
         input.parse_comma_separated(|input| {
             // background-color can only be in the last element, so if it
             // is parsed anywhere before, the value is invalid.
             if background_color.is_some() {
                 return Err(StyleParseError::UnspecifiedError.into());
             }
 
@@ -192,18 +196,22 @@
                     sub_properties="background-position-x background-position-y"
                     spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
     use properties::longhands::{background_position_x, background_position_y};
     use values::specified::AllowQuirks;
     use values::specified::position::Position;
 
     pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                -> Result<Longhands, ParseError<'i>> {
-        let mut position_x = background_position_x::SpecifiedValue(Vec::new());
-        let mut position_y = background_position_y::SpecifiedValue(Vec::new());
+        // Vec grows from 0 to 4 by default on first push().  So allocate with
+        // capacity 1, so in the common case of only one item we don't way
+        // overallocate.  Note that we always push at least one item if parsing
+        // succeeds.
+        let mut position_x = background_position_x::SpecifiedValue(Vec::with_capacity(1));
+        let mut position_y = background_position_y::SpecifiedValue(Vec::with_capacity(1));
         let mut any = false;
 
         input.parse_comma_separated(|input| {
             let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
             position_x.0.push(value.horizontal);
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
--- a/servo/components/style/properties/shorthand/mask.mako.rs
+++ b/servo/components/style/properties/shorthand/mask.mako.rs
@@ -33,17 +33,21 @@
                 % endif
             }
         }
     }
 
     pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                -> Result<Longhands, ParseError<'i>> {
         % for name in "image mode position_x position_y size repeat origin clip composite".split():
-            let mut mask_${name} = mask_${name}::SpecifiedValue(Vec::new());
+            // Vec grows from 0 to 4 by default on first push().  So allocate
+            // with capacity 1, so in the common case of only one item we don't
+            // way overallocate.  Note that we always push at least one item if
+            // parsing succeeds.
+            let mut mask_${name} = mask_${name}::SpecifiedValue(Vec::with_capacity(1));
         % endfor
 
         input.parse_comma_separated(|input| {
             % for name in "image mode position size repeat origin clip composite".split():
                 let mut ${name} = None;
             % endfor
             loop {
                 if image.is_none() {
@@ -178,18 +182,22 @@
                     sub_properties="mask-position-x mask-position-y"
                     spec="https://drafts.csswg.org/css-masks-4/#the-mask-position">
     use properties::longhands::{mask_position_x,mask_position_y};
     use values::specified::position::Position;
     use parser::Parse;
 
     pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                -> Result<Longhands, ParseError<'i>> {
-        let mut position_x = mask_position_x::SpecifiedValue(Vec::new());
-        let mut position_y = mask_position_y::SpecifiedValue(Vec::new());
+        // Vec grows from 0 to 4 by default on first push().  So allocate with
+        // capacity 1, so in the common case of only one item we don't way
+        // overallocate.  Note that we always push at least one item if parsing
+        // succeeds.
+        let mut position_x = mask_position_x::SpecifiedValue(Vec::with_capacity(1));
+        let mut position_y = mask_position_y::SpecifiedValue(Vec::with_capacity(1));
         let mut any = false;
 
         input.parse_comma_separated(|input| {
             let value = Position::parse(context, input)?;
             position_x.0.push(value.horizontal);
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
--- a/servo/components/style/values/generics/image.rs
+++ b/servo/components/style/values/generics/image.rs
@@ -6,38 +6,80 @@
 //!
 //! [images]: https://drafts.csswg.org/css-images/#image-values
 
 use Atom;
 use cssparser::serialize_identifier;
 use custom_properties::SpecifiedValue;
 use std::fmt;
 use style_traits::ToCss;
-use values::computed::ComputedValueAsSpecified;
+use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
 
 /// An [image].
 ///
 /// [image]: https://drafts.csswg.org/css-images/#image-values
-#[derive(Clone, PartialEq, ToComputedValue)]
+#[derive(Clone, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Image<Gradient, MozImageRect, ImageUrl> {
     /// A `<url()>` image.
     Url(ImageUrl),
-    /// A `<gradient>` image.
-    Gradient(Gradient),
-    /// A `-moz-image-rect` image
-    Rect(MozImageRect),
+    /// A `<gradient>` image.  Gradients are rather large, and not nearly as
+    /// common as urls, so we box them here to keep the size of this enum sane.
+    Gradient(Box<Gradient>),
+    /// A `-moz-image-rect` image.  Also fairly large and rare.
+    Rect(Box<MozImageRect>),
     /// A `-moz-element(# <element-id>)`
     Element(Atom),
     /// A paint worklet image.
     /// https://drafts.css-houdini.org/css-paint-api/
     #[cfg(feature = "servo")]
     PaintWorklet(PaintWorklet),
 }
 
+// Can't just use derive(ToComputedValue) on Image, because when trying to do
+// "impl<T> ToComputedValue for Box<T>" the Rust compiler complains that
+// "impl<T> ToComputedValue for T where T: ComputedValueAsSpecified + Clone"
+// aleady implements ToComputedValue for std::boxed::Box<_> and hence we have
+// conflicting implementations.
+impl<Gradient: ToComputedValue,
+     MozImageRect: ToComputedValue,
+     ImageUrl: ToComputedValue> ToComputedValue for Image<Gradient, MozImageRect, ImageUrl> {
+    type ComputedValue = Image<<Gradient as ToComputedValue>::ComputedValue,
+                               <MozImageRect as ToComputedValue>::ComputedValue,
+                               <ImageUrl as ToComputedValue>::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            Image::Url(ref url) => Image::Url(url.to_computed_value(context)),
+            Image::Gradient(ref gradient) =>
+                Image::Gradient(Box::new(gradient.to_computed_value(context))),
+            Image::Rect(ref rect) => Image::Rect(Box::new(rect.to_computed_value(context))),
+            Image::Element(ref atom) => Image::Element(atom.to_computed_value(context)),
+            #[cfg(feature = "servo")]
+            Image::PaintWorklet(ref worklet) => Image::PaintWorklet(worklet.to_computed_value(context)),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            Image::Url(ref url) => Image::Url(ImageUrl::from_computed_value(url)),
+            Image::Gradient(ref boxed_gradient) =>
+                Image::Gradient(Box::new(Gradient::from_computed_value(&*boxed_gradient))),
+            Image::Rect(ref boxed_rect) =>
+                Image::Rect(Box::new(MozImageRect::from_computed_value(&*boxed_rect))),
+            Image::Element(ref atom) => Image::Element(Atom::from_computed_value(atom)),
+            #[cfg(feature = "servo")]
+            Image::PaintWorklet(ref worklet) =>
+                Image::PaintWorklet(PaintWorklet::from_computed_value(worklet)),
+        }
+    }
+}
+
 /// A CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, PartialEq, ToComputedValue)]
 pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color, Angle> {
     /// Gradients can be linear or radial.
     pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position, Angle>,
     /// The color stops and interpolation hints.
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -133,30 +133,30 @@ impl Parse for Image {
         if let Ok(mut url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
             #[cfg(feature = "gecko")]
             {
                 url.build_image_value();
             }
             return Ok(GenericImage::Url(url));
         }
         if let Ok(gradient) = input.try(|i| Gradient::parse(context, i)) {
-            return Ok(GenericImage::Gradient(gradient));
+            return Ok(GenericImage::Gradient(Box::new(gradient)));
         }
         #[cfg(feature = "servo")]
         {
             if let Ok(paint_worklet) = input.try(|i| PaintWorklet::parse(context, i)) {
                 return Ok(GenericImage::PaintWorklet(paint_worklet));
             }
         }
         if let Ok(mut image_rect) = input.try(|input| MozImageRect::parse(context, input)) {
             #[cfg(feature = "gecko")]
             {
                 image_rect.url.build_image_value();
             }
-            return Ok(GenericImage::Rect(image_rect));
+            return Ok(GenericImage::Rect(Box::new(image_rect)));
         }
         Ok(GenericImage::Element(Image::parse_element(input)?))
     }
 }
 
 impl Image {
     /// Creates an already specified image value from an already resolved URL
     /// for insertion in the cascade.
--- a/servo/mach.bat
+++ b/servo/mach.bat
@@ -5,17 +5,17 @@ IF EXIST "%ProgramFiles(x86)%" (
 ) ELSE (
   set "ProgramFiles32=%ProgramFiles%"
 )
 
 set VC14VARS=%VS140COMNTOOLS%..\..\VC\vcvarsall.bat
 IF EXIST "%VC14VARS%" (
   set "VS_VCVARS=%VC14VARS%"
 ) ELSE (
-  for %%e in (Enterprise Professional Community) do (
+  for %%e in (Enterprise Professional Community BuildTools) do (
     IF EXIST "%ProgramFiles32%\Microsoft Visual Studio\2017\%%e\VC\Auxiliary\Build\vcvarsall.bat" (
       set "VS_VCVARS=%ProgramFiles32%\Microsoft Visual Studio\2017\%%e\VC\Auxiliary\Build\vcvarsall.bat"
     )
   )
 )
 
 IF EXIST "%VS_VCVARS%" (
   IF NOT DEFINED Platform (
--- a/servo/tests/unit/stylo/size_of.rs
+++ b/servo/tests/unit/stylo/size_of.rs
@@ -6,16 +6,18 @@ use selectors::gecko_like_types as dummi
 use servo_arc::Arc;
 use std::mem::{size_of, align_of};
 use style;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::data::{ElementData, ElementStyles, RestyleData};
 use style::gecko::selector_parser as real;
 use style::properties::ComputedValues;
 use style::rule_tree::{RuleNode, StrongRuleNode};
+use style::values::computed;
+use style::values::specified;
 
 #[test]
 fn size_of_selectors_dummy_types() {
     assert_eq!(size_of::<dummies::PseudoClass>(), size_of::<real::NonTSPseudoClass>());
     assert_eq!(align_of::<dummies::PseudoClass>(), align_of::<real::NonTSPseudoClass>());
 
     assert_eq!(size_of::<dummies::PseudoElement>(), size_of::<real::PseudoElement>());
     assert_eq!(align_of::<dummies::PseudoElement>(), align_of::<real::PseudoElement>());
@@ -42,8 +44,16 @@ size_of_test!(test_size_of_application_d
 
 // FIXME(bholley): This can shrink with a little bit of work.
 // See https://github.com/servo/servo/issues/17280
 size_of_test!(test_size_of_rule_node, RuleNode, 80);
 
 // This is huge, but we allocate it on the stack and then never move it,
 // we only pass `&mut SourcePropertyDeclaration` references around.
 size_of_test!(test_size_of_parsed_declaration, style::properties::SourcePropertyDeclaration, 704);
+
+size_of_test!(test_size_of_computed_image, computed::image::Image, 40);
+size_of_test!(test_size_of_specified_image, specified::image::Image, 40);
+
+// FIXME(bz): These can shrink if we move the None_ value inside the
+// enum instead of paying an extra word for the Either discriminant.
+size_of_test!(test_size_of_computed_image_layer, computed::image::ImageLayer, 48);
+size_of_test!(test_size_of_specified_image_layer, specified::image::ImageLayer, 48);