Bug 1233470 - make browser/modules eslintable, r?Mossop draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Thu, 17 Dec 2015 18:31:08 +0000
changeset 316064 82bd0ac1e9f3b3267d60d127758fa5771a5e9fc9
parent 316015 0dd30d9b7c9a86a0033c9529f2b540e9ffcba7ee
child 512127 53f2caf07342bed5b9eb6e952bd6668e6b6be407
push id8510
push usergijskruitbosch@gmail.com
push dateThu, 17 Dec 2015 21:23:36 +0000
reviewersMossop
bugs1233470
milestone46.0a1
Bug 1233470 - make browser/modules eslintable, r?Mossop
.eslintignore
browser/modules/BrowserUITelemetry.jsm
browser/modules/Chat.jsm
browser/modules/ContentClick.jsm
browser/modules/ContentObservers.jsm
browser/modules/ContentSearch.jsm
browser/modules/ContentWebRTC.jsm
browser/modules/DirectoryLinksProvider.jsm
browser/modules/ProcessHangMonitor.jsm
browser/modules/Social.jsm
browser/modules/WindowsPreviewPerTab.jsm
browser/modules/moz.build
browser/modules/test/browser_BrowserUITelemetry_buckets.js
browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
toolkit/modules/AppConstants.jsm
--- a/.eslintignore
+++ b/.eslintignore
@@ -78,17 +78,16 @@ browser/components/shell/**
 browser/components/tabview/**
 browser/components/translation/**
 browser/components/uitour/**
 browser/experiments/**
 browser/extensions/pdfjs/**
 browser/extensions/shumway/**
 browser/fuel/**
 browser/locales/**
-browser/modules/**
 
 # Loop specific exclusions
 
 # This file currently uses a non-standard (and not on a standards track)
 # if statement within catch.
 browser/extensions/loop/content/modules/MozLoopWorker.js
 # This file currently uses es7 features eslint issue:
 # https://github.com/eslint/espree/issues/125
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -529,18 +529,22 @@ this.BrowserUITelemetry = {
         // else, it's provided by an add-on, and we won't record it.
       }
     }
 
     // Now go through the items in the palette to see what default
     // items are in there.
     let paletteItems =
       CustomizableUI.getUnusedWidgets(aWindow.gNavToolbox.palette);
-    let defaultRemoved = [item.id for (item of paletteItems)
-                          if (DEFAULT_ITEMS.indexOf(item.id) != -1)];
+    let defaultRemoved = [];
+    for (item of paletteItems) {
+      if (DEFAULT_ITEMS.indexOf(item.id) != -1) {
+        defaultRemoved.push(item.id);
+      }
+    }
 
     result.defaultKept = defaultKept;
     result.defaultMoved = defaultMoved;
     result.nondefaultAdded = nondefaultAdded;
     result.defaultRemoved = defaultRemoved;
 
     // Next, determine how many add-on provided toolbars exist.
     let addonToolbars = 0;
--- a/browser/modules/Chat.jsm
+++ b/browser/modules/Chat.jsm
@@ -65,17 +65,17 @@ var Chat = {
       while (winEnum.hasMoreElements()) {
         let win = winEnum.getNext();
         let chatbar = win.document.getElementById("pinnedchats");
         if (!chatbar)
           continue;
 
         // Make a new array instead of the live NodeList so this iterator can be
         // used for closing/deleting.
-        let chatboxes = [c for (c of chatbar.children)];
+        let chatboxes = [...chatbar.children];
         for (let chatbox of chatboxes) {
           yield chatbox;
         }
       }
 
       // include standalone chat windows
       winEnum = Services.wm.getEnumerator("Social:Chat");
       while (winEnum.hasMoreElements()) {
@@ -239,17 +239,17 @@ var Chat = {
    *                                set object.
    */
   loadButtonSet: function(chatbox, buttonSet = kDefaultButtonSet) {
     if (!buttonSet)
       return;
 
     // When the buttonSet is coming from an XML attribute, it will be a string.
     if (typeof buttonSet == "string") {
-      buttonSet = [for (button of buttonSet.split(",")) button.trim()];
+      buttonSet = buttonSet.split(",").map(button => button.trim());
     }
 
     // Make sure to keep the current set around.
     chatbox.setAttribute("buttonSet", [...buttonSet].join(","));
 
     let isUndocked = !chatbox.chatbar;
     let document = chatbox.ownerDocument;
     let titlebarNode = document.getAnonymousElementByAttribute(chatbox, "class",
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -59,17 +59,17 @@ var ContentClick = {
       return;
     }
 
     // Note: We don't need the sidebar code here.
 
     // This part is based on handleLinkClick.
     var where = window.whereToOpenLink(json);
     if (where == "current")
-      return false;
+      return;
 
     // Todo(903022): code for where == save
 
     let params = { charset: browser.characterSet,
                    referrerURI: browser.documentURI,
                    referrerPolicy: json.referrerPolicy,
                    noReferrer: json.noReferrer };
     window.openLinkIn(json.href, where, params);
--- a/browser/modules/ContentObservers.jsm
+++ b/browser/modules/ContentObservers.jsm
@@ -30,14 +30,17 @@ var gEMEUIObserver = function(subject, t
 function getMessageManagerForWindow(aContentWindow) {
   let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDocShell)
                          .sameTypeRootTreeItem
                          .QueryInterface(Ci.nsIInterfaceRequestor);
   try {
     // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
     return ir.getInterface(Ci.nsIContentFrameMessageManager);
-  } catch(e if e.result == Cr.NS_NOINTERFACE) {
-    return null;
+  } catch(e) {
+    if (e.result == Cr.NS_NOINTERFACE) {
+      return null;
+    }
+    throw e;
   }
 }
 
 Services.obs.addObserver(gEMEUIObserver, "mediakeys-request", false);
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -207,17 +207,17 @@ this.ContentSearch = {
 
   _processEventQueue: function () {
     if (this._currentEventPromise || !this._eventQueue.length) {
       return;
     }
 
     let event = this._eventQueue.shift();
 
-    return this._currentEventPromise = Task.spawn(function* () {
+    this._currentEventPromise = Task.spawn(function* () {
       try {
         yield this["_on" + event.type](event.data);
       } catch (err) {
         Cu.reportError(err);
       } finally {
         this._currentEventPromise = null;
         this._processEventQueue();
       }
--- a/browser/modules/ContentWebRTC.jsm
+++ b/browser/modules/ContentWebRTC.jsm
@@ -332,16 +332,19 @@ function getInnerWindowIDForWindow(aCont
 function getMessageManagerForWindow(aContentWindow) {
   let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDocShell)
                          .sameTypeRootTreeItem
                          .QueryInterface(Ci.nsIInterfaceRequestor);
   try {
     // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
     return ir.getInterface(Ci.nsIContentFrameMessageManager);
-  } catch(e if e.result == Cr.NS_NOINTERFACE) {
-    return null;
+  } catch(e) {
+    if (e.result == Cr.NS_NOINTERFACE) {
+      return null;
+    }
+    throw e;
   }
 }
 
 function processShutdown() {
   ContentWebRTC.uninit();
 }
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -768,17 +768,17 @@ var DirectoryLinksProvider = {
     NewTabUtils.links.addObserver(this);
 
     // ensure remote new tab doesn't go beyond aurora
     if (!AppConstants.RELEASE_BUILD) {
       RemoteNewTabUtils.placesProvider.addObserver(this);
       RemoteNewTabUtils.links.addObserver(this);
     }
 
-    return Task.spawn(function() {
+    return Task.spawn(function*() {
       // get the last modified time of the links file if it exists
       let doesFileExists = yield OS.File.exists(this._directoryFilePath);
       if (doesFileExists) {
         let fileInfo = yield OS.File.stat(this._directoryFilePath);
         this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate);
       }
       // read frequency cap file
       yield this._readFrequencyCapFile();
@@ -911,17 +911,17 @@ var DirectoryLinksProvider = {
    * @return the chosen suggested tile, or undefined if there isn't one
    */
   _updateSuggestedTile: function() {
     let sortedLinks = NewTabUtils.getProviderLinks(this);
 
     if (!sortedLinks) {
       // If NewTabUtils.links.resetCache() is called before getting here,
       // sortedLinks may be undefined.
-      return;
+      return undefined;
     }
 
     // Delete the current suggested tile, if one exists.
     let initialLength = sortedLinks.length;
     if (initialLength) {
       let mostFrecentLink = sortedLinks[0];
       if (mostFrecentLink.targetedSite) {
         this._callObservers("onLinkChanged", {
@@ -934,17 +934,17 @@ var DirectoryLinksProvider = {
     }
 
     if (this._topSitesWithSuggestedLinks.size == 0 ||
         !this._shouldUpdateSuggestedTile() ||
         this._isSuggestedTileBlocked()) {
       // There are no potential suggested links we can show or not
       // enough history for a suggested tile, or suggested tile was
       // recently blocked and wait time interval has not decayed yet
-      return;
+      return undefined;
     }
 
     // Create a flat list of all possible links we can show as suggested.
     // Note that many top sites may map to the same suggested links, but we only
     // want to count each suggested link once (based on url), thus possibleLinks is a map
     // from url to suggestedLink. Thus, each link has an equal chance of being chosen at
     // random from flattenedLinks if it appears only once.
     let nextTimeout;
@@ -989,17 +989,17 @@ var DirectoryLinksProvider = {
     // setup timeout check for starting or ending campaigns
     if (nextTimeout) {
       this._setupCampaignTimeCheck(nextTimeout - Date.now());
     }
 
     // We might have run out of possible links to show
     let numLinks = possibleLinks.size;
     if (numLinks == 0) {
-      return;
+      return undefined;
     }
 
     let flattenedLinks = [...possibleLinks.values()];
 
     // Choose our suggested link at random
     let suggestedIndex = Math.floor(Math.random() * numLinks);
     let chosenSuggestedLink = flattenedLinks[suggestedIndex];
 
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -6,16 +6,17 @@
 "use strict";
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 this.EXPORTED_SYMBOLS = ["ProcessHangMonitor"];
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 /**
  * This JSM is responsible for observing content process hang reports
  * and asking the user what to do about them. See nsIHangReport for
  * the platform interface.
  */
 
@@ -171,17 +172,17 @@ var ProcessHangMonitor = {
   /**
    * If there is a hang report associated with the selected browser in
    * |win|, invoke |func| on that report and stop notifying the user
    * about it.
    */
   handleUserInput: function(win, func) {
     let report = this.findActiveReport(win.gBrowser.selectedBrowser);
     if (!report) {
-      return;
+      return null;
     }
     this.removeActiveReport(report);
 
     return func(report);
   },
 
   observe: function(subject, topic, data) {
     switch (topic) {
@@ -317,27 +318,25 @@ var ProcessHangMonitor = {
       {
         label: bundle.getString("processHang.button_wait.label"),
         accessKey: bundle.getString("processHang.button_wait.accessKey"),
         callback: function() {
           ProcessHangMonitor.waitLonger(win);
         }
       }];
 
-#ifdef MOZ_DEV_EDITION
-    if (report.hangType == report.SLOW_SCRIPT) {
+    if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {
       buttons.push({
         label: bundle.getString("processHang.button_debug.label"),
         accessKey: bundle.getString("processHang.button_debug.accessKey"),
         callback: function() {
           ProcessHangMonitor.debugScript(win);
         }
       });
     }
-#endif
 
     nb.appendNotification(bundle.getString("processHang.label"),
                           "process-hang",
                           "chrome://browser/content/aboutRobots-icon.png",
                           nb.PRIORITY_WARNING_HIGH, buttons);
   },
 
   /**
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -132,17 +132,21 @@ this.Social = {
         let provider = Social._getProviderFromOrigin(origin);
         provider.reload();
       }
     });
     return deferred.promise;
   },
 
   _updateWorkerState: function(enable) {
-    [p.enabled = enable for (p of Social.providers) if (p.enabled != enable)];
+    for (let p of Social.providers) {
+      if (p.enabled != enable) {
+        p.enabled = enable;
+      }
+    }
   },
 
   // Called to update our cache of providers and set the current provider
   _updateProviderCache: function (providers) {
     this.providers = providers;
     Services.obs.notifyObservers(null, "social:providers-changed", null);
   },
 
--- a/browser/modules/WindowsPreviewPerTab.jsm
+++ b/browser/modules/WindowsPreviewPerTab.jsm
@@ -538,17 +538,22 @@ TabWindow.prototype = {
   },
 
   updateTabOrdering: function () {
     let previews = this.previews;
     let tabs = this.tabbrowser.tabs;
 
     // Previews are internally stored using a map, so we need to iterate the
     // tabbrowser's array of tabs to retrieve previews in the same order.
-    let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))];
+    let inorder = [];
+    for (let t of tabs) {
+      if (previews.has(t)) {
+        inorder.push(previews.get(t));
+      }
+    }
 
     // Since the internal taskbar array has not yet been updated we must force
     // on it the sorting order of our local array.  To do so we must walk
     // the local array backwards, otherwise we would send move requests in the
     // wrong order.  See bug 522610 for details.
     for (let i = inorder.length - 1; i >= 0; i--) {
       inorder[i].move(inorder[i + 1] || null);
     }
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -28,33 +28,30 @@ EXTRA_JS_MODULES += [
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'HiddenFrame.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'PluginContent.jsm',
+    'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabGroupsMigrator.jsm',
     'TransientPrefs.jsm',
     'WebappManager.jsm',
     'webrtcUI.jsm',
 ]
 
-EXTRA_PP_JS_MODULES += [
-    'ProcessHangMonitor.jsm'
-]
-
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
     ]
 
 if CONFIG['NIGHTLY_BUILD']:
--- a/browser/modules/test/browser_BrowserUITelemetry_buckets.js
+++ b/browser/modules/test/browser_BrowserUITelemetry_buckets.js
@@ -1,102 +1,97 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/*
-    WHERE'S MAH BUCKET?!
-          \
-                     ___
-                  .-9 9 `\
-                =(:(::)=  ;
-                  ||||     \
-                  ||||      `-.
-                 ,\|\|         `,
-                /                \
-               ;                  `'---.,
-               |                         `\
-               ;                     /     |
-               \                    |      /
-                )           \  __,.--\    /
-             .-' \,..._\     \`   .-'  .-'
-            `-=``      `:    |  /-/-/`
-                         `.__/
-*/
-
-"use strict";
-
-
-function generatorTest() {
-  let s = {};
-  Components.utils.import("resource:///modules/BrowserUITelemetry.jsm", s);
-  let BUIT = s.BrowserUITelemetry;
-
-  registerCleanupFunction(function() {
-    BUIT.setBucket(null);
-  });
-
-
-  // setBucket
-  is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be default bucket");
-  BUIT.setBucket("mah-bucket");
-  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
-  BUIT.setBucket(null);
-  is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be reset to default");
-
-
-  // _toTimeStr
-  is(BUIT._toTimeStr(10), "10ms", "Checking time string reprentation, 10ms");
-  is(BUIT._toTimeStr(1000 + 10), "1s10ms", "Checking time string reprentation, 1s10ms");
-  is(BUIT._toTimeStr((20 * 1000) + 10), "20s10ms", "Checking time string reprentation, 20s10ms");
-  is(BUIT._toTimeStr(60 * 1000), "1m", "Checking time string reprentation, 1m");
-  is(BUIT._toTimeStr(3 * 60 * 1000), "3m", "Checking time string reprentation, 3m");
-  is(BUIT._toTimeStr((3 * 60 * 1000) + 1), "3m1ms", "Checking time string reprentation, 3m1ms");
-  is(BUIT._toTimeStr((60 * 60 * 1000) + (10 * 60 * 1000)), "1h10m", "Checking time string reprentation, 1h10m");
-  is(BUIT._toTimeStr(100 * 60 * 60 * 1000), "100h", "Checking time string reprentation, 100h");
-
-
-  // setExpiringBucket
-  BUIT.setExpiringBucket("walrus", [1001, 2001, 3001, 10001]);
-  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "1s1ms",
-     "Bucket should be expiring and have time step of 1s1ms");
-
-  waitForCondition(function() {
-    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "2s1ms");
-  }, nextStep, "Bucket should be expiring and have time step of 2s1ms");
-  yield undefined;
-
-  waitForCondition(function() {
-    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "3s1ms");
-  }, nextStep, "Bucket should be expiring and have time step of 3s1ms");
-  yield undefined;
-
-
-  // Interupt previous expiring bucket
-  BUIT.setExpiringBucket("walrus2", [1002, 2002]);
-  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "1s2ms",
-     "Should be new expiring bucket, with time step of 1s2ms");
-
-  waitForCondition(function() {
-    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "2s2ms");
-  }, nextStep, "Should be new expiring bucket, with time step of 2s2ms");
-  yield undefined;
-
-
-  // Let expiring bucket expire
-  waitForCondition(function() {
-    return BUIT.currentBucket == BUIT.BUCKET_DEFAULT;
-  }, nextStep, "Bucket should have expired, default bucket should now be active");
-  yield undefined;
-
-
-  // Interupt expiring bucket with normal bucket
-  BUIT.setExpiringBucket("walrus3", [1003, 2003]);
-  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus3" + BUIT.BUCKET_SEPARATOR + "1s3ms",
-     "Should be new expiring bucket, with time step of 1s3ms");
-
-  BUIT.setBucket("mah-bucket");
-  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
-
-  waitForCondition(function() {
-    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "mah-bucket");
-  }, nextStep, "Next step of old expiring bucket shouldn't have progressed");
-  yield undefined;
-}
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+    WHERE'S MAH BUCKET?!
+          \
+                     ___
+                  .-9 9 `\
+                =(:(::)=  ;
+                  ||||     \
+                  ||||      `-.
+                 ,\|\|         `,
+                /                \
+               ;                  `'---.,
+               |                         `\
+               ;                     /     |
+               \                    |      /
+                )           \  __,.--\    /
+             .-' \,..._\     \`   .-'  .-'
+            `-=``      `:    |  /-/-/`
+                         `.__/
+*/
+
+"use strict";
+
+
+add_task(function* testBUIT() {
+  let s = {};
+  Components.utils.import("resource:///modules/BrowserUITelemetry.jsm", s);
+  let BUIT = s.BrowserUITelemetry;
+
+  registerCleanupFunction(function() {
+    BUIT.setBucket(null);
+  });
+
+
+  // setBucket
+  is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be default bucket");
+  BUIT.setBucket("mah-bucket");
+  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
+  BUIT.setBucket(null);
+  is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be reset to default");
+
+
+  // _toTimeStr
+  is(BUIT._toTimeStr(10), "10ms", "Checking time string reprentation, 10ms");
+  is(BUIT._toTimeStr(1000 + 10), "1s10ms", "Checking time string reprentation, 1s10ms");
+  is(BUIT._toTimeStr((20 * 1000) + 10), "20s10ms", "Checking time string reprentation, 20s10ms");
+  is(BUIT._toTimeStr(60 * 1000), "1m", "Checking time string reprentation, 1m");
+  is(BUIT._toTimeStr(3 * 60 * 1000), "3m", "Checking time string reprentation, 3m");
+  is(BUIT._toTimeStr((3 * 60 * 1000) + 1), "3m1ms", "Checking time string reprentation, 3m1ms");
+  is(BUIT._toTimeStr((60 * 60 * 1000) + (10 * 60 * 1000)), "1h10m", "Checking time string reprentation, 1h10m");
+  is(BUIT._toTimeStr(100 * 60 * 60 * 1000), "100h", "Checking time string reprentation, 100h");
+
+
+  // setExpiringBucket
+  BUIT.setExpiringBucket("walrus", [1001, 2001, 3001, 10001]);
+  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "1s1ms",
+     "Bucket should be expiring and have time step of 1s1ms");
+
+  yield waitForConditionPromise(function() {
+    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "2s1ms");
+  }, "Bucket should be expiring and have time step of 2s1ms");
+
+  yield waitForConditionPromise(function() {
+    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "3s1ms");
+  }, "Bucket should be expiring and have time step of 3s1ms");
+
+
+  // Interupt previous expiring bucket
+  BUIT.setExpiringBucket("walrus2", [1002, 2002]);
+  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "1s2ms",
+     "Should be new expiring bucket, with time step of 1s2ms");
+
+  yield waitForConditionPromise(function() {
+    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "2s2ms");
+  }, "Should be new expiring bucket, with time step of 2s2ms");
+
+
+  // Let expiring bucket expire
+  yield waitForConditionPromise(function() {
+    return BUIT.currentBucket == BUIT.BUCKET_DEFAULT;
+  }, "Bucket should have expired, default bucket should now be active");
+
+
+  // Interupt expiring bucket with normal bucket
+  BUIT.setExpiringBucket("walrus3", [1003, 2003]);
+  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus3" + BUIT.BUCKET_SEPARATOR + "1s3ms",
+     "Should be new expiring bucket, with time step of 1s3ms");
+
+  BUIT.setBucket("mah-bucket");
+  is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
+
+  yield waitForConditionPromise(function() {
+    return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "mah-bucket");
+  }, "Next step of old expiring bucket shouldn't have progressed");
+});
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -195,28 +195,28 @@ function promiseDirectoryDownloadOnPrefC
     return observer.deferred.promise.then(() => {
       DirectoryLinksProvider.removeObserver(observer);
     });
   }
   return Promise.resolve();
 }
 
 function promiseSetupDirectoryLinksProvider(options = {}) {
-  return Task.spawn(function() {
+  return Task.spawn(function*() {
     let linksURL = options.linksURL || kTestURL;
     yield DirectoryLinksProvider.init();
     yield promiseDirectoryDownloadOnPrefChange(kLocalePref, options.locale || "en-US");
     yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, linksURL);
     do_check_eq(DirectoryLinksProvider._linksURL, linksURL);
     DirectoryLinksProvider._lastDownloadMS = options.lastDownloadMS || 0;
   });
 }
 
 function promiseCleanDirectoryLinksProvider() {
-  return Task.spawn(function() {
+  return Task.spawn(function*() {
     yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US");
     yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL);
     yield DirectoryLinksProvider._clearFrequencyCap();
     yield DirectoryLinksProvider._loadInadjacentSites();
     DirectoryLinksProvider._lastDownloadMS  = 0;
     DirectoryLinksProvider.reset();
   });
 }
@@ -287,17 +287,17 @@ add_task(function test_shouldUpdateSugge
   do_check_eq(DirectoryLinksProvider._getCurrentTopSiteCount(), 8);
   isIdentical(NewTabUtils.getProviderLinks(), []);
   do_check_eq(DirectoryLinksProvider._shouldUpdateSuggestedTile(), true);
 
   // Cleanup
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
-add_task(function test_updateSuggestedTile() {
+add_task(function* test_updateSuggestedTile() {
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
 
   // Initial setup
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
@@ -416,17 +416,17 @@ add_task(function test_updateSuggestedTi
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
-add_task(function test_suggestedLinksMap() {
+add_task(function* test_suggestedLinksMap() {
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   // Ensure the suggested tiles were not considered directory tiles.
   do_check_eq(links.length, 1);
@@ -455,17 +455,17 @@ add_task(function test_suggestedLinksMap
       linkCopy.explanation = "";
       isIdentical(suggestedLinksItr.next().value, linkCopy);
     }
   })
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_topSitesWithSuggestedLinks() {
+add_task(function* test_topSitesWithSuggestedLinks() {
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
   // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils
   let origGetProviderLinks = NewTabUtils.getProviderLinks;
@@ -507,17 +507,17 @@ add_task(function test_topSitesWithSugge
   DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
   isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
 
   // Cleanup.
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
 });
 
-add_task(function test_suggestedAttributes() {
+add_task(function* test_suggestedAttributes() {
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(",");
   let imageURI = "https://image/";
@@ -564,17 +564,17 @@ add_task(function test_suggestedAttribut
 
   // Cleanup.
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
 });
 
-add_task(function test_frequencyCappedSites_views() {
+add_task(function* test_frequencyCappedSites_views() {
   Services.prefs.setCharPref(kPingUrlPref, "");
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
@@ -641,17 +641,17 @@ add_task(function test_frequencyCappedSi
   // Cleanup.
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
-add_task(function test_frequencyCappedSites_click() {
+add_task(function* test_frequencyCappedSites_click() {
   Services.prefs.setCharPref(kPingUrlPref, "");
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
@@ -711,17 +711,17 @@ add_task(function test_frequencyCappedSi
   // Cleanup.
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
-add_task(function test_reportSitesAction() {
+add_task(function* test_reportSitesAction() {
   yield DirectoryLinksProvider.init();
   let deferred, expectedPath, expectedPost;
   let done = false;
   server.registerPrefixHandler(kPingPath, (aRequest, aResponse) => {
     if (done) {
       return;
     }
 
@@ -818,93 +818,93 @@ add_task(function test_reportSitesAction
 
   // Click the 2th site / 1th tile
   expectedPost.click = 1;
   yield sendPingAndTest("click", "click", 2);
 
   done = true;
 });
 
-add_task(function test_fetchAndCacheLinks_local() {
+add_task(function* test_fetchAndCacheLinks_local() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   // Trigger cache of data or chrome uri files in profD
   yield DirectoryLinksProvider._fetchAndCacheLinks(kTestURL);
   let data = yield readJsonFile();
   isIdentical(data, kURLData);
 });
 
-add_task(function test_fetchAndCacheLinks_remote() {
+add_task(function* test_fetchAndCacheLinks_remote() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   // this must trigger directory links json download and save it to cache file
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL + "%LOCALE%");
   do_check_eq(gLastRequestPath, kExamplePath + "en-US");
   let data = yield readJsonFile();
   isIdentical(data, kHttpHandlerData[kExamplePath]);
 });
 
-add_task(function test_fetchAndCacheLinks_malformedURI() {
+add_task(function* test_fetchAndCacheLinks_malformedURI() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   let someJunk = "some junk";
   try {
     yield DirectoryLinksProvider._fetchAndCacheLinks(someJunk);
     do_throw("Malformed URIs should fail")
   } catch (e) {
     do_check_eq(e, "Error fetching " + someJunk)
   }
 
   // File should be empty.
   let data = yield readJsonFile();
   isIdentical(data, "");
 });
 
-add_task(function test_fetchAndCacheLinks_unknownHost() {
+add_task(function* test_fetchAndCacheLinks_unknownHost() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   let nonExistentServer = "http://localhost:56789/";
   try {
     yield DirectoryLinksProvider._fetchAndCacheLinks(nonExistentServer);
     do_throw("BAD URIs should fail");
   } catch (e) {
     do_check_true(e.startsWith("Fetching " + nonExistentServer + " results in error code: "))
   }
 
   // File should be empty.
   let data = yield readJsonFile();
   isIdentical(data, "");
 });
 
-add_task(function test_fetchAndCacheLinks_non200Status() {
+add_task(function* test_fetchAndCacheLinks_non200Status() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kFailURL);
   do_check_eq(gLastRequestPath, kFailPath);
   let data = yield readJsonFile();
   isIdentical(data, {});
 });
 
 // To test onManyLinksChanged observer, trigger a fetch
-add_task(function test_DirectoryLinksProvider__linkObservers() {
+add_task(function* test_DirectoryLinksProvider__linkObservers() {
   yield DirectoryLinksProvider.init();
 
   let testObserver = new LinksChangeObserver();
   DirectoryLinksProvider.addObserver(testObserver);
   do_check_eq(DirectoryLinksProvider._observers.size, 1);
   DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
 
   yield testObserver.deferred.promise;
   DirectoryLinksProvider._removeObservers();
   do_check_eq(DirectoryLinksProvider._observers.size, 0);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider__prefObserver_url() {
+add_task(function* test_DirectoryLinksProvider__prefObserver_url() {
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
 
   let links = yield fetchData();
   do_check_eq(links.length, 1);
   let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
   isIdentical(links, expectedData);
 
   // tests these 2 things:
@@ -923,29 +923,29 @@ add_task(function test_DirectoryLinksPro
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl + " ");
   // we now should see empty links
   newLinks = yield fetchData();
   isIdentical(newLinks, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getLinks_noDirectoryData() {
+add_task(function* test_DirectoryLinksProvider_getLinks_noDirectoryData() {
   let data = {
     "directory": [],
   };
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 0);
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getLinks_badData() {
+add_task(function* test_DirectoryLinksProvider_getLinks_badData() {
   let data = {
     "en-US": {
       "en-US": [{url: "http://example.com", title: "US"}],
     },
   };
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
@@ -961,17 +961,17 @@ add_task(function test_DirectoryLinksPro
   do_check_true(DirectoryLinksProvider._needsDownload);
   DirectoryLinksProvider._lastDownloadMS = Date.now();
   do_check_false(DirectoryLinksProvider._needsDownload);
   DirectoryLinksProvider._lastDownloadMS = Date.now() - (60*60*24 + 1)*1000;
   do_check_true(DirectoryLinksProvider._needsDownload);
   DirectoryLinksProvider._lastDownloadMS = 0;
 });
 
-add_task(function test_DirectoryLinksProvider_fetchAndCacheLinksIfNecessary() {
+add_task(function* test_DirectoryLinksProvider_fetchAndCacheLinksIfNecessary() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   // explicitly change source url to cause the download during setup
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL+" "});
   yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary();
 
   // inspect lastDownloadMS timestamp which should be 5 seconds less then now()
   let lastDownloadMS = DirectoryLinksProvider._lastDownloadMS;
@@ -1003,17 +1003,17 @@ add_task(function test_DirectoryLinksPro
   let downloadPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
   let anotherPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
   do_check_true(downloadPromise === anotherPromise);
   yield downloadPromise;
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_fetchDirectoryOnPrefChange() {
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnPrefChange() {
   yield DirectoryLinksProvider.init();
 
   let testObserver = new LinksChangeObserver();
   DirectoryLinksProvider.addObserver(testObserver);
 
   yield cleanJsonFile();
   // ensure that provider does not think it needs to download
   do_check_false(DirectoryLinksProvider._needsDownload);
@@ -1024,57 +1024,57 @@ add_task(function test_DirectoryLinksPro
   yield testObserver.deferred.promise;
   do_check_eq(gLastRequestPath, kExamplePath);
   let data = yield readJsonFile();
   isIdentical(data, kHttpHandlerData[kExamplePath]);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_fetchDirectoryOnShow() {
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnShow() {
   yield promiseSetupDirectoryLinksProvider();
 
   // set lastdownload to 0 to make DirectoryLinksProvider want to download
   DirectoryLinksProvider._lastDownloadMS = 0;
   do_check_true(DirectoryLinksProvider._needsDownload);
 
   // download should happen on view
   yield DirectoryLinksProvider.reportSitesAction([], "view");
   do_check_true(DirectoryLinksProvider._lastDownloadMS != 0);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_fetchDirectoryOnInit() {
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnInit() {
   // ensure preferences are set to defaults
   yield promiseSetupDirectoryLinksProvider();
   // now clean to provider, so we can init it again
   yield promiseCleanDirectoryLinksProvider();
 
   yield cleanJsonFile();
   yield DirectoryLinksProvider.init();
   let data = yield readJsonFile();
   isIdentical(data, kURLData);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getLinksFromCorruptedFile() {
+add_task(function* test_DirectoryLinksProvider_getLinksFromCorruptedFile() {
   yield promiseSetupDirectoryLinksProvider();
 
   // write bogus json to a file and attempt to fetch from it
   let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.profileDir, DIRECTORY_LINKS_FILE);
   yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
   let data = yield fetchData();
   isIdentical(data, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getAllowedLinks() {
+add_task(function* test_DirectoryLinksProvider_getAllowedLinks() {
   let data = {"directory": [
     {url: "ftp://example.com"},
     {url: "http://example.net"},
     {url: "javascript:5"},
     {url: "https://example.com"},
     {url: "httpJUNKjavascript:42"},
     {url: "data:text/plain,hi"},
     {url: "http/bork:eh"},
@@ -1085,17 +1085,17 @@ add_task(function test_DirectoryLinksPro
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining url should be http and https
   do_check_eq(links[0].url, data["directory"][1].url);
   do_check_eq(links[1].url, data["directory"][3].url);
 });
 
-add_task(function test_DirectoryLinksProvider_getAllowedImages() {
+add_task(function* test_DirectoryLinksProvider_getAllowedImages() {
   let data = {"directory": [
     {url: "http://example.com", imageURI: "ftp://example.com"},
     {url: "http://example.com", imageURI: "http://example.net"},
     {url: "http://example.com", imageURI: "javascript:5"},
     {url: "http://example.com", imageURI: "https://example.com"},
     {url: "http://example.com", imageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", imageURI: "data:text/plain,hi"},
     {url: "http://example.com", imageURI: "http/bork:eh"},
@@ -1106,17 +1106,17 @@ add_task(function test_DirectoryLinksPro
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining images should be https and data
   do_check_eq(links[0].imageURI, data["directory"][3].imageURI);
   do_check_eq(links[1].imageURI, data["directory"][5].imageURI);
 });
 
-add_task(function test_DirectoryLinksProvider_getAllowedImages_base() {
+add_task(function* test_DirectoryLinksProvider_getAllowedImages_base() {
   let data = {"directory": [
     {url: "http://example1.com", imageURI: "https://example.com"},
     {url: "http://example2.com", imageURI: "https://tiles.cdn.mozilla.net"},
     {url: "http://example3.com", imageURI: "https://tiles2.cdn.mozilla.net"},
     {url: "http://example4.com", enhancedImageURI: "https://mozilla.net"},
     {url: "http://example5.com", imageURI: "data:text/plain,hi"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
@@ -1130,17 +1130,17 @@ add_task(function test_DirectoryLinksPro
 
   // The only remaining images should be https with mozilla.net or data URI
   do_check_eq(links[0].url, data["directory"][1].url);
   do_check_eq(links[1].url, data["directory"][2].url);
   do_check_eq(links[2].url, data["directory"][3].url);
   do_check_eq(links[3].url, data["directory"][4].url);
 });
 
-add_task(function test_DirectoryLinksProvider_getAllowedEnhancedImages() {
+add_task(function* test_DirectoryLinksProvider_getAllowedEnhancedImages() {
   let data = {"directory": [
     {url: "http://example.com", enhancedImageURI: "ftp://example.com"},
     {url: "http://example.com", enhancedImageURI: "http://example.net"},
     {url: "http://example.com", enhancedImageURI: "javascript:5"},
     {url: "http://example.com", enhancedImageURI: "https://example.com"},
     {url: "http://example.com", enhancedImageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", enhancedImageURI: "data:text/plain,hi"},
     {url: "http://example.com", enhancedImageURI: "http/bork:eh"},
@@ -1151,17 +1151,17 @@ add_task(function test_DirectoryLinksPro
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining enhancedImages should be http and https and data
   do_check_eq(links[0].enhancedImageURI, data["directory"][3].enhancedImageURI);
   do_check_eq(links[1].enhancedImageURI, data["directory"][5].enhancedImageURI);
 });
 
-add_task(function test_DirectoryLinksProvider_getEnhancedLink() {
+add_task(function* test_DirectoryLinksProvider_getEnhancedLink() {
   let data = {"enhanced": [
     {url: "http://example.net", enhancedImageURI: "data:,net1"},
     {url: "http://example.com", enhancedImageURI: "data:,com1"},
     {url: "http://example.com", enhancedImageURI: "data:,com2"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
@@ -1203,17 +1203,17 @@ add_task(function test_DirectoryLinksPro
   dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   links = yield fetchData();
   do_check_eq(links.length, 0); // There are no directory links.
   checkEnhanced("http://example.net", undefined);
   checkEnhanced("http://example.com", "data:,fresh");
 });
 
-add_task(function test_DirectoryLinksProvider_enhancedURIs() {
+add_task(function* test_DirectoryLinksProvider_enhancedURIs() {
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let data = {
     "suggested": [
       {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", adgroup_name: "Test", frecent_sites: ["test.com"]}
@@ -1274,17 +1274,17 @@ add_task(function test_DirectoryLinksPro
   // Turn off DNT header
   Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
   checkDefault(true);
 
   // Clean up
   Services.prefs.clearUserPref("privacy.donottrackheader.value");
 });
 
-add_task(function test_timeSensetiveSuggestedTiles() {
+add_task(function* test_timeSensetiveSuggestedTiles() {
   // make tile json with start and end dates
   let testStartTime = Date.now();
   // start date is now + 1 seconds
   let startDate = new Date(testStartTime + 1000);
   // end date is now + 3 seconds
   let endDate = new Date(testStartTime + 3000);
   let suggestedTile = Object.assign({
     time_limits: {
@@ -1445,17 +1445,17 @@ add_task(function test_setupStartEndTime
   DirectoryLinksProvider._setupStartEndTime(link);
   do_check_false(link.startTime);
 
   link.time_limits.start = "20150501T00:00:00"
   DirectoryLinksProvider._setupStartEndTime(link);
   do_check_false(link.startTime);
 });
 
-add_task(function test_DirectoryLinksProvider_frequencyCapSetup() {
+add_task(function* test_DirectoryLinksProvider_frequencyCapSetup() {
   yield promiseSetupDirectoryLinksProvider();
   yield DirectoryLinksProvider.init();
 
   yield promiseCleanDirectoryLinksProvider();
   yield DirectoryLinksProvider._readFrequencyCapFile();
   isIdentical(DirectoryLinksProvider._frequencyCaps, {});
 
   // setup few links
@@ -1521,17 +1521,17 @@ add_task(function test_DirectoryLinksPro
   // make sure all keys but "3" and "7" are deleted
   Object.keys(DirectoryLinksProvider._frequencyCaps).forEach(key => {
     do_check_true(key == "3" || key == "7");
   });
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getFrequencyCapLogic() {
+add_task(function* test_DirectoryLinksProvider_getFrequencyCapLogic() {
   yield promiseSetupDirectoryLinksProvider();
   yield DirectoryLinksProvider.init();
 
   // setup suggested links
   DirectoryLinksProvider._updateFrequencyCapSettings({
     url: "1",
     frequency_caps: {daily: 2, total: 4}
   });
@@ -1572,17 +1572,17 @@ add_task(function test_DirectoryLinksPro
   // testing unknown url should always return false
   do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("nosuch.url"));
 
   // reset _wasToday back to original function
   DirectoryLinksProvider._wasToday = _wasTodayOrig;
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getFrequencyCapReportSiteAction() {
+add_task(function* test_DirectoryLinksProvider_getFrequencyCapReportSiteAction() {
   yield promiseSetupDirectoryLinksProvider();
   yield DirectoryLinksProvider.init();
 
   // setup suggested links
   DirectoryLinksProvider._updateFrequencyCapSettings({
     url: "bar.com",
     frequency_caps: {daily: 2, total: 4}
   });
@@ -1600,17 +1600,17 @@ add_task(function test_DirectoryLinksPro
   // read file content and ensure that view counters are updated
   let data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
   do_check_eq(data["bar.com"].dailyViews, 1);
   do_check_eq(data["bar.com"].totalViews, 1);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_ClickRemoval() {
+add_task(function* test_DirectoryLinksProvider_ClickRemoval() {
   yield promiseSetupDirectoryLinksProvider();
   yield DirectoryLinksProvider.init();
   let landingUrl = "http://foo.com";
 
   // setup suggested links
   DirectoryLinksProvider._updateFrequencyCapSettings({
     url: landingUrl,
     frequency_caps: {daily: 2, total: 4}
@@ -1704,17 +1704,17 @@ add_task(function test_DirectoryLinksPro
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_anonymous() {
   do_check_true(DirectoryLinksProvider._newXHR().mozAnon);
 });
 
-add_task(function test_sanitizeExplanation() {
+add_task(function* test_sanitizeExplanation() {
   // Note: this is a basic test to ensure we applied sanitization to the link explanation.
   // Full testing for appropriate sanitization is done in parser/xml/test/unit/test_sanitizer.js.
   let data = {"suggested": [suggestedTile5]};
   let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data));
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
@@ -1722,17 +1722,17 @@ add_task(function test_sanitizeExplanati
   do_check_eq(suggestedSites.indexOf("eviltarget.com"), 0);
   do_check_eq(suggestedSites.length, 1);
 
   let suggestedLink = [...DirectoryLinksProvider._suggestedLinks.get(suggestedSites[0]).values()][0];
   do_check_eq(suggestedLink.explanation, "This is an evil tile X muhahaha");
   do_check_eq(suggestedLink.targetedName, "WE ARE EVIL ");
 });
 
-add_task(function test_inadjecentSites() {
+add_task(function* test_inadjecentSites() {
   let suggestedTile = Object.assign({
     check_inadjacency: true
   }, suggestedTile1);
 
   // Initial setup
   let topSites = ["1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
@@ -1896,17 +1896,17 @@ add_task(function test_inadjecentSites()
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider._inadjacentSitesUrl = origInadjacentSitesUrl;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_reportPastImpressions() {
+add_task(function* test_reportPastImpressions() {
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
   let data = {
@@ -2000,17 +2000,17 @@ add_task(function test_reportPastImpress
   yield sendPingAndTest("click", "block", 2);
 
   // Cleanup.
   done = true;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
-add_task(function test_blockSuggestedTiles() {
+add_task(function* test_blockSuggestedTiles() {
   // Initial setup
   let suggestedTile = suggestedTile1;
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -47,16 +47,23 @@ this.AppConstants = Object.freeze({
 
   MOZ_OFFICIAL_BRANDING:
 #ifdef MOZ_OFFICIAL_BRANDING
   true,
 #else
   false,
 #endif
 
+  MOZ_DEV_EDITION:
+#ifdef MOZ_DEV_EDITION
+  true,
+#else
+  false,
+#endif
+
   MOZ_SERVICES_HEALTHREPORT:
 #ifdef MOZ_SERVICES_HEALTHREPORT
   true,
 #else
   false,
 #endif
 
   MOZ_DATA_REPORTING: