merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 15 Sep 2015 14:59:25 +0200
changeset 295099 6d08fcbb0431ea105614dab3b7df75880a160ff4
parent 295081 d813c4a18837ae45993e95c9c15ae0182e3dec56 (current diff)
parent 295098 d9824a8871c8b73e0c79ad837deb766677e4baab (diff)
child 295156 5d61714cf5c3ec2440e9491ba30893ac37ca06c2
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.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 fx-team to mozilla-central a=merge
mobile/android/base/resources/drawable-hdpi/menu_light.png
mobile/android/base/resources/drawable-xhdpi/menu_light.png
mobile/android/modules/MatchstickApp.jsm
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -8,16 +8,17 @@
  */
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://suggested.com/",
     imageURI: "data:image/png;base64,helloWORLD3",
     title: "title",
     type: "affiliate",
+    adgroup_name: "test",
     frecent_sites: ["example0.com"]
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
--- a/browser/base/content/test/newtab/browser_newtab_bug1145428.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
@@ -10,16 +10,17 @@
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://example.com/landing/page.html",
     imageURI: "data:image/png;base64,helloWORLD3",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
+    adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
--- a/browser/base/content/test/newtab/browser_newtab_bug1178586.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
@@ -8,16 +8,17 @@
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://example.com/hardlanding/page.html",
     imageURI: "data:image/png;base64,helloWORLD3",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
+    adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -3,16 +3,17 @@
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 let suggestedLink = {
   url: "http://example1.com/2",
   imageURI: "data:image/png;base64,helloWORLD3",
   title: "title2",
   type: "affiliate",
+  adgroup_name: "Technology",
   frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
 };
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "enhanced": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
@@ -134,31 +135,30 @@ function runTests() {
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
 
   // Suggested link was not enhanced by directory link with same domain
   ({type, enhanced, title, suggested} = getData(0));
   is(type, "affiliate", "suggested link is affiliate");
   is(enhanced, "", "suggested link has no enhanced image");
   is(title, "title2");
-  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> visitors") > -1, "Suggested for 'webdev education'");
+  ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
 
   // Enhanced history link shows up second
   ({type, enhanced, title, suggested} = getData(1));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
   is(getData(9), null, "there is a suggested link followed by an enhanced history link and the remaining history links");
 
 
 
-  // Test override category/adgroup name.
-  suggestedLink.adgroup_name = "Technology";
+  // Test no override category/adgroup name.
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + JSON.stringify({"suggested": [suggestedLink]}));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
@@ -171,18 +171,18 @@ function runTests() {
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
 
 
-  // Test server provided explanation string without category override.
-  delete suggestedLink.adgroup_name;
+  // Test server provided explanation string with category override.
+  suggestedLink.adgroup_name = "webdev education";
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -55,17 +55,17 @@ extensions.registerAPI((extension, conte
           callback = args[0];
         } else {
           [getInfo, callback] = args;
         }
         let window = WindowManager.topWindow;
         runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
       },
 
-      getAll: function(getAll, callback) {
+      getAll: function(getInfo, callback) {
         let e = Services.wm.getEnumerator("navigator:browser");
         let windows = [];
         while (e.hasMoreElements()) {
           let window = e.getNext();
           windows.push(WindowManager.convert(extension, window, getInfo));
         }
         runSafe(context, callback, windows);
       },
@@ -126,17 +126,20 @@ extensions.registerAPI((extension, conte
       },
 
       update: function(windowId, updateInfo, callback) {
         let window = WindowManager.getWindow(windowId);
         if (updateInfo.focused) {
           Services.focus.activeWindow = window;
         }
         // TODO: All the other properties...
-        runSafe(context, callback, WindowManager.convert(extension, window));
+
+        if (callback) {
+          runSafe(context, callback, WindowManager.convert(extension, window));
+        }
       },
 
       remove: function(windowId, callback) {
         let window = WindowManager.getWindow(windowId);
         window.close();
 
         let listener = () => {
           AllWindowEvents.removeListener("domwindowclosed", listener);
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
 
 [browser_extensions_simple.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_update.js]
+[browser_ext_windows_update.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -0,0 +1,51 @@
+add_task(function* () {
+  function promiseWaitForFocus(aWindow) {
+    return new Promise(function(aResolve, aReject) {
+      waitForFocus(function() {
+        ok(Services.focus.activeWindow === aWindow, "correct window focused");
+        aResolve();
+      }, aWindow);
+    });
+  }
+
+  let window1 = window;
+  let window2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  Services.focus.activeWindow = window2;
+  yield promiseWaitForFocus(window2);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["windows"]
+    },
+
+    background: function() {
+      browser.windows.getAll(undefined, function(wins) {
+        browser.test.assertEq(wins.length, 2, "should have two windows");
+
+        // Sort the unfocused window to the lower index.
+        wins.sort(function(win1, win2) {
+          if (win1.focused === win2.focused) {
+            return 0;
+          }
+
+          return win1.focused ? 1 : -1;
+        });
+
+        browser.windows.update(wins[0].id, {focused: true}, function() {
+          browser.test.sendMessage("check");
+        });
+
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("check");
+
+  yield promiseWaitForFocus(window1);
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(window2);
+});
\ No newline at end of file
--- a/browser/devtools/canvasdebugger/snapshotslist.js
+++ b/browser/devtools/canvasdebugger/snapshotslist.js
@@ -415,24 +415,25 @@ let SnapshotsListView = Heritage.extend(
         screenshot: null
       };
       let functionCalls = snapshotItem.attachment.calls;
       let thumbnails = snapshotItem.attachment.thumbnails;
       let screenshot = snapshotItem.attachment.screenshot;
 
       // Prepare all the function calls for serialization.
       yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
-        let { type, name, file, line, argsPreview, callerPreview } = call;
+        let { type, name, file, line, timestamp, argsPreview, callerPreview } = call;
         return call.getDetails().then(({ stack }) => {
           data.calls[i] = {
             type: type,
             name: name,
             file: file,
             line: line,
             stack: stack,
+            timestamp: timestamp,
             argsPreview: argsPreview,
             callerPreview: callerPreview
           };
         });
       });
 
       // Prepare all the thumbnails for serialization.
       yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
--- a/browser/devtools/canvasdebugger/test/browser.ini
+++ b/browser/devtools/canvasdebugger/test/browser.ini
@@ -47,8 +47,10 @@ skip-if = e10s # bug 1102301 - leaks whi
 [browser_canvas-frontend-slider-01.js]
 [browser_canvas-frontend-slider-02.js]
 [browser_canvas-frontend-snapshot-select-01.js]
 [browser_canvas-frontend-snapshot-select-02.js]
 [browser_canvas-frontend-stepping.js]
 [browser_canvas-frontend-stop-01.js]
 [browser_canvas-frontend-stop-02.js]
 [browser_canvas-frontend-stop-03.js]
+[browser_profiling-canvas.js]
+[browser_profiling-webgl.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/canvasdebugger/test/browser_profiling-canvas.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+  let currentTime = window.performance.now();
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+  let navigated = once(target, "navigate");
+
+  yield front.setup({ reload: true });
+  ok(true, "The front was setup up successfully.");
+
+  yield navigated;
+  ok(true, "Target automatically navigated when the front was set up.");
+
+  let snapshotActor = yield front.recordAnimationFrame();
+  ok(snapshotActor,
+    "A snapshot actor was sent after recording.");
+
+  let animationOverview = yield snapshotActor.getOverview();
+  ok(animationOverview,
+    "An animation overview could be retrieved after recording.");
+
+  let functionCalls = animationOverview.calls;
+  ok(functionCalls,
+    "An array of function call actors was sent after recording.");
+  is(functionCalls.length, 8,
+    "The number of function call actors is correct.");
+
+  info("Check the timestamps of function calls");
+
+  for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
+    ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
+    ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
+    ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
+  }
+
+  yield removeTab(target.tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/canvasdebugger/test/browser_profiling-webgl.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+  let currentTime = window.performance.now();
+  let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
+
+  let navigated = once(target, "navigate");
+
+  yield front.setup({ reload: true });
+  ok(true, "The front was setup up successfully.");
+
+  yield navigated;
+  ok(true, "Target automatically navigated when the front was set up.");
+
+  let snapshotActor = yield front.recordAnimationFrame();
+  ok(snapshotActor,
+    "A snapshot actor was sent after recording.");
+
+  let animationOverview = yield snapshotActor.getOverview();
+  ok(animationOverview,
+    "An animation overview could be retrieved after recording.");
+
+  let functionCalls = animationOverview.calls;
+  ok(functionCalls,
+    "An array of function call actors was sent after recording.");
+  is(functionCalls.length, 3,
+    "The number of function call actors is correct.");
+
+  info("Check the timestamps of function calls");
+
+  for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
+    ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
+    ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
+    ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
+  }
+
+  yield removeTab(target.tab);
+  finish();
+}
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
   doc_markup_not_displayed.html
   doc_markup_pagesize_01.html
   doc_markup_pagesize_02.html
   doc_markup_search.html
   doc_markup_svg_attributes.html
   doc_markup_toggle.html
   doc_markup_tooltip.png
   doc_markup_xul.xul
+  frame-script-utils.js
   head.js
   helper_attributes_test_runner.js
   helper_events_test_runner.js
   helper_outerhtml_test_runner.js
   lib_jquery_1.0.js
   lib_jquery_1.1.js
   lib_jquery_1.2_min.js
   lib_jquery_1.3_min.js
--- a/browser/devtools/markupview/test/browser_markupview_keybindings_04.js
+++ b/browser/devtools/markupview/test/browser_markupview_keybindings_04.js
@@ -40,20 +40,38 @@ add_task(function*() {
 });
 
 function assertNodeSelected(inspector, tagName) {
   is(inspector.selection.nodeFront.tagName.toLowerCase(), tagName,
     `The <${tagName}> node is selected`);
 }
 
 function* selectWithBrowserMenu(inspector) {
-  yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
-    type: "contextmenu",
-    button: 2
-  }, gBrowser.selectedBrowser);
+  // This test can't use BrowserTestUtils.synthesizeMouseAtCenter()
+  // method (see below) since it causes intermittent test failures.
+  // So, we are introducing a new "Test:MarkupView:SynthesizeMouse" event
+  // that is handled in the content scope. The main difference between
+  // this new event and BrowserTestUtils library is EventUtils library.
+  // While BrowserTestUtils is using:
+  // chrome://mochikit/content/tests/SimpleTest/EventUtils.js
+  // (see: AsyncUtilsContent.js)
+  // ... this test requires:
+  // chrome://marionette/content/EventUtils.js
+  // (see markupview/test/frame-script-utils.js)
+  // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1199180
+  yield executeInContent("Test:MarkupView:SynthesizeMouse", {
+    center: true,
+    selector: "div",
+    options: {type: "contextmenu", button: 2}
+  });
+
+  //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+  //  type: "contextmenu",
+  //  button: 2
+  //}, gBrowser.selectedBrowser);
 
   // nsContextMenu also requires the popupNode to be set, but we can't set it to
   // node under e10s as it's a CPOW, not a DOM node. But under e10s,
   // nsContextMenu won't use the property anyway, so just try/catching is ok.
   try {
     document.popupNode = getNode("div");
   } catch (e) {}
 
@@ -65,17 +83,26 @@ function* selectWithBrowserMenu(inspecto
   contentAreaContextMenu.hidePopup();
   contextMenu.hiding();
 
   yield inspector.once("inspector-updated");
 }
 
 function* selectWithElementPicker(inspector) {
   yield inspector.toolbox.highlighterUtils.startPicker();
-  yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
-    type: "mousemove",
-  }, gBrowser.selectedBrowser);
+
+  yield executeInContent("Test:MarkupView:SynthesizeMouse", {
+    center: true,
+    selector: "div",
+    options: {type: "mousemove"}
+  });
+
+  // Read comment in selectWithBrowserMenu() method.
+  //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+  //  type: "mousemove",
+  //}, gBrowser.selectedBrowser);
+
   executeInContent("Test:SynthesizeKey", {
     key: "VK_RETURN",
     options: {}
   }, false);
   yield inspector.once("inspector-updated");
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/frame-script-utils.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci} = Components;
+const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+                          .getService(Ci.mozIJSSubScriptLoader);
+let EventUtils = {};
+subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
+
+/**
+ * Synthesize a mouse event on an element. This handler doesn't send a message
+ * back. Consumers should listen to specific events on the inspector/highlighter
+ * to know when the event got synthesized.
+ * @param {Object} msg The msg.data part expects the following properties:
+ * - {Number} x
+ * - {Number} y
+ * - {Boolean} center If set to true, x/y will be ignored and
+ *             synthesizeMouseAtCenter will be used instead
+ * - {Object} options Other event options
+ * - {String} selector An optional selector that will be used to find the node to
+ *            synthesize the event on, if msg.objects doesn't contain the CPOW.
+ * The msg.objects part should be the element.
+ * @param {Object} data Event detail properties:
+ */
+addMessageListener("Test:MarkupView:SynthesizeMouse", function(msg) {
+  let {x, y, center, options, selector} = msg.data;
+  let {node} = msg.objects;
+
+  if (!node && selector) {
+    node = content.document.querySelector(selector);
+  }
+
+  if (center) {
+    EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView);
+  } else {
+    EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView);
+  }
+
+  // Most consumers won't need to listen to this message, unless they want to
+  // wait for the mouse event to be synthesized and don't have another event
+  // to listen to instead.
+  sendAsyncMessage("Test:MarkupView:SynthesizeMouse");
+});
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -46,16 +46,17 @@ registerCleanupFunction(function*() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/";
 const CHROME_BASE = "chrome://mochitests/content/browser/browser/devtools/markupview/test/";
 const COMMON_FRAME_SCRIPT_URL = "chrome://browser/content/devtools/frame-script-utils.js";
+const MARKUPVIEW_FRAME_SCRIPT_URL = CHROME_BASE + "frame-script-utils.js";
 
 /**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
  * @return a promise that resolves to the tab object when the url is loaded
  */
 function addTab(url) {
   info("Adding a new tab with URL: '" + url + "'");
@@ -66,16 +67,17 @@ function addTab(url) {
   // For now, we just make sure the window is focused.
   window.focus();
 
   let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
   let linkedBrowser = tab.linkedBrowser;
 
   info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
   linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
+  linkedBrowser.messageManager.loadFrameScript(MARKUPVIEW_FRAME_SCRIPT_URL, false);
 
   linkedBrowser.addEventListener("load", function onload() {
     linkedBrowser.removeEventListener("load", onload, true);
     info("URL '" + url + "' loading complete");
     def.resolve(tab);
   }, true);
 
   return def.promise;
--- a/browser/devtools/performance/modules/logic/frame-utils.js
+++ b/browser/devtools/performance/modules/logic/frame-utils.js
@@ -549,13 +549,37 @@ function getFrameInfo (node, options) {
     data.totalSizePercentage = node.byteSize / totalBytes * 100;
     data.ALLOCATION_DATA_CALCULATED = true;
   }
 
   return data;
 }
 
 exports.getFrameInfo = getFrameInfo;
+
+/**
+ * Takes an inverted ThreadNode and searches its youngest frames for
+ * a FrameNode with matching location.
+ *
+ * @param {ThreadNode} threadNode
+ * @param {string} location
+ * @return {?FrameNode}
+ */
+function findFrameByLocation (threadNode, location) {
+  if (!threadNode.inverted) {
+    throw new Error("FrameUtils.findFrameByLocation only supports leaf nodes in an inverted tree.");
+  }
+
+  let calls = threadNode.calls;
+  for (let i = 0; i < calls.length; i++) {
+    if (calls[i].location === location) {
+      return calls[i];
+    }
+  }
+  return null;
+}
+
+exports.findFrameByLocation = findFrameByLocation;
 exports.computeIsContentAndCategory = computeIsContentAndCategory;
 exports.parseLocation = parseLocation;
 exports.getInflatedFrameCache = getInflatedFrameCache;
 exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
 exports.InflatedFrame = InflatedFrame;
--- a/browser/devtools/performance/modules/logic/jit.js
+++ b/browser/devtools/performance/modules/logic/jit.js
@@ -264,70 +264,72 @@ const IMPLEMENTATION_NAMES = Object.keys
  * Takes data from a FrameNode and computes rendering positions for
  * a stacked mountain graph, to visualize JIT optimization tiers over time.
  *
  * @param {FrameNode} frameNode
  *                    The FrameNode who's optimizations we're iterating.
  * @param {Array<number>} sampleTimes
  *                        An array of every sample time within the range we're counting.
  *                        From a ThreadNode's `sampleTimes` property.
- * @param {number} op.startTime
- *                 The start time of the first sample.
- * @param {number} op.endTime
- *                 The end time of the last sample.
- * @param {number} op.resolution
- *                 The maximum amount of possible data points returned.
- *                 Also determines the size in milliseconds of each bucket
- *                 via `(endTime - startTime) / resolution`
+ * @param {number} bucketSize
+ *                 Size of each bucket in milliseconds.
+ *                 `duration / resolution = bucketSize` in OptimizationsGraph.
  * @return {?Array<object>}
  */
-function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, endTime, resolution }) {
-  if (!frameNode.hasOptimizations()) {
-    return;
-  }
-
-  let tierData = frameNode.getOptimizationTierData();
-  let duration = endTime - startTime;
+function createTierGraphDataFromFrameNode (frameNode, sampleTimes, bucketSize) {
+  let tierData = frameNode.getTierData();
   let stringTable = frameNode._stringTable;
   let output = [];
   let implEnum;
 
   let tierDataIndex = 0;
   let nextOptSample = tierData[tierDataIndex];
 
   // Bucket data
   let samplesInCurrentBucket = 0;
   let currentBucketStartTime = sampleTimes[0];
   let bucket = [];
-  // Size of each bucket in milliseconds
-  let bucketSize = Math.ceil(duration / resolution);
+
+  // Store previous data point so we can have straight vertical lines
+  let previousValues;
 
   // Iterate one after the samples, so we can finalize the last bucket
   for (let i = 0; i <= sampleTimes.length; i++) {
     let sampleTime = sampleTimes[i];
 
     // If this sample is in the next bucket, or we're done
     // checking sampleTimes and on the last iteration, finalize previous bucket
     if (sampleTime >= (currentBucketStartTime + bucketSize) ||
         i >= sampleTimes.length) {
 
       let dataPoint = {};
-      dataPoint.ys = [];
-      dataPoint.x = currentBucketStartTime;
+      dataPoint.values = [];
+      dataPoint.delta = currentBucketStartTime;
 
       // Map the opt site counts as a normalized percentage (0-1)
       // of its count in context of total samples this bucket
       for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) {
-        dataPoint.ys[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
+        dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
       }
+
+      // Push the values from the previous bucket to the same time
+      // as the current bucket so we get a straight vertical line.
+      if (previousValues) {
+        let data = Object.create(null);
+        data.values = previousValues;
+        data.delta = currentBucketStartTime;
+        output.push(data);
+      }
+
       output.push(dataPoint);
 
       // Set the new start time of this bucket and reset its count
       currentBucketStartTime += bucketSize;
       samplesInCurrentBucket = 0;
+      previousValues = dataPoint.values;
       bucket = [];
     }
 
     // If this sample observed an optimization in this frame, record it
     if (nextOptSample && nextOptSample.time === sampleTime) {
       // If no implementation defined, it was the "interpreter".
       implEnum = IMPLEMENTATION_MAP[stringTable[nextOptSample.implementation] || "interpreter"];
       bucket[implEnum] = (bucket[implEnum] || 0) + 1;
--- a/browser/devtools/performance/modules/logic/tree-model.js
+++ b/browser/devtools/performance/modules/logic/tree-model.js
@@ -31,16 +31,18 @@ function ThreadNode(thread, options = {}
     throw new Error("ThreadNode requires both `startTime` and `endTime`.");
   }
   this.samples = 0;
   this.sampleTimes = [];
   this.youngestFrameSamples = 0;
   this.calls = [];
   this.duration = options.endTime - options.startTime;
   this.nodeType = "Thread";
+  this.inverted = options.invertTree;
+
   // Total bytesize of all allocations if enabled
   this.byteSize = 0;
   this.youngestFrameByteSize = 0;
 
   let { samples, stackTable, frameTable, stringTable } = thread;
 
   // Nothing to do if there are no samples.
   if (samples.data.length === 0) {
@@ -227,20 +229,18 @@ ThreadNode.prototype = {
           calls = prevCalls;
         }
 
         let frameNode = getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame,
                                           mutableFrameKeyOptions.isMetaCategoryOut,
                                           leafTable);
         if (isLeaf) {
           frameNode.youngestFrameSamples++;
-          if (inflatedFrame.optimizations) {
-            frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
-                                        sampleTime, stringTable);
-          }
+          frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
+                                      sampleTime, stringTable);
 
           if (byteSize) {
             frameNode.youngestFrameByteSize += byteSize;
           }
         }
 
         // Don't overcount flattened recursive frames.
         if (!shouldFlatten) {
@@ -405,57 +405,57 @@ function FrameNode(frameKey, { location,
   this.key = frameKey;
   this.location = location;
   this.line = line;
   this.youngestFrameSamples = 0;
   this.samples = 0;
   this.calls = [];
   this.isContent = !!isContent;
   this._optimizations = null;
-  this._tierData = null;
+  this._tierData = [];
   this._stringTable = null;
   this.isMetaCategory = !!isMetaCategory;
   this.category = category;
   this.nodeType = "Frame";
   this.byteSize = 0;
   this.youngestFrameByteSize = 0;
 }
 
 FrameNode.prototype = {
   /**
    * Take optimization data observed for this frame.
    *
    * @param object optimizationSite
    *               Any JIT optimization information attached to the current
    *               sample. Lazily inflated via stringTable.
    * @param number implementation
-   *               JIT implementation used for this observed frame (interpreter,
-   *               baseline, ion);
+   *               JIT implementation used for this observed frame (baseline, ion);
+   *               can be null indicating "interpreter"
    * @param number time
    *               The time this optimization occurred.
    * @param object stringTable
    *               The string table used to inflate the optimizationSite.
    */
   _addOptimizations: function (site, implementation, time, stringTable) {
     // Simply accumulate optimization sites for now. Processing is done lazily
     // by JITOptimizations, if optimization information is actually displayed.
     if (site) {
       let opts = this._optimizations;
       if (opts === null) {
         opts = this._optimizations = [];
-        this._stringTable = stringTable;
       }
       opts.push(site);
+    }
 
-      if (this._tierData === null) {
-        this._tierData = [];
-      }
-      // Record type of implementation used and the sample time
-      this._tierData.push({ implementation, time });
+    if (!this._stringTable) {
+      this._stringTable = stringTable;
     }
+
+    // Record type of implementation used and the sample time
+    this._tierData.push({ implementation, time });
   },
 
   _clone: function (samples, size) {
     let newNode = new FrameNode(this.key, this, this.isMetaCategory);
     newNode._merge(this, samples, size);
     return newNode;
   },
 
@@ -469,27 +469,39 @@ FrameNode.prototype = {
     if (otherNode.youngestFrameSamples > 0) {
       this.youngestFrameSamples += samples;
     }
 
     if (otherNode.youngestFrameByteSize > 0) {
       this.youngestFrameByteSize += otherNode.youngestFrameByteSize;
     }
 
+    if (this._stringTable === null) {
+      this._stringTable = otherNode._stringTable;
+    }
+
     if (otherNode._optimizations) {
+      if (!this._optimizations) {
+        this._optimizations = [];
+      }
       let opts = this._optimizations;
-      if (opts === null) {
-        opts = this._optimizations = [];
-        this._stringTable = otherNode._stringTable;
-      }
       let otherOpts = otherNode._optimizations;
       for (let i = 0; i < otherOpts.length; i++) {
-        opts.push(otherOpts[i]);
+       opts.push(otherOpts[i]);
       }
     }
+
+    if (otherNode._tierData.length) {
+      let tierData = this._tierData;
+      let otherTierData = otherNode._tierData;
+      for (let i = 0; i < otherTierData.length; i++) {
+        tierData.push(otherTierData[i]);
+      }
+      tierData.sort((a, b) => a.time - b.time);
+    }
   },
 
   /**
    * Returns the parsed location and additional data describing
    * this frame. Uses cached data if possible. Takes the following
    * options:
    *
    * @param {ThreadNode} options.root
@@ -524,22 +536,19 @@ FrameNode.prototype = {
   getOptimizations: function () {
     if (!this._optimizations) {
       return null;
     }
     return new JITOptimizations(this._optimizations, this._stringTable);
   },
 
   /**
-   * Returns the optimization tiers used overtime.
+   * Returns the tiers used overtime.
    *
-   * @return {?Array<object>}
+   * @return {Array<object>}
    */
-  getOptimizationTierData: function () {
-    if (!this._tierData) {
-      return null;
-    }
+  getTierData: function () {
     return this._tierData;
   }
 };
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
--- a/browser/devtools/performance/modules/widgets/graphs.js
+++ b/browser/devtools/performance/modules/widgets/graphs.js
@@ -7,53 +7,61 @@
  * This file contains the base line graph that all Performance line graphs use.
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget");
 const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
+const MountainGraphWidget = require("devtools/shared/widgets/MountainGraphWidget");
 const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyRequireGetter(this, "colorUtils",
   "devtools/css-color", true);
 loader.lazyRequireGetter(this, "getColor",
   "devtools/shared/theme", true);
 loader.lazyRequireGetter(this, "ProfilerGlobal",
   "devtools/performance/global");
 loader.lazyRequireGetter(this, "L10N",
   "devtools/performance/global", true);
 loader.lazyRequireGetter(this, "MarkersOverview",
   "devtools/performance/markers-overview", true);
+loader.lazyRequireGetter(this, "createTierGraphDataFromFrameNode",
+  "devtools/performance/jit", true);
 
 /**
  * For line graphs
  */
 const HEIGHT = 35; // px
 const STROKE_WIDTH = 1; // px
 const DAMPEN_VALUES = 0.95;
 const CLIPHEAD_LINE_COLOR = "#666";
 const SELECTION_LINE_COLOR = "#555";
-const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
-const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
-const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
+const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue";
+const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green";
+const MEMORY_GRAPH_COLOR_NAME = "graphs-blue";
 
 /**
  * For timeline overview
  */
 const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
 const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
 const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
 
 /**
+ * For optimization graph
+ */
+const OPTIMIZATIONS_GRAPH_RESOLUTION = 100;
+
+/**
  * A base class for performance graphs to inherit from.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the overview.
  * @param string metric
  *        The unit of measurement for this graph.
  */
 function PerformanceGraph(parent, metric) {
@@ -81,17 +89,17 @@ PerformanceGraph.prototype = Heritage.ex
 
   /**
    * Sets the theme via `theme` to either "light" or "dark",
    * and updates the internal styling to match. Requires a redraw
    * to see the effects.
    */
   setTheme: function (theme) {
     theme = theme || "light";
-    let mainColor = getColor(this.mainColor || "highlight-blue", theme);
+    let mainColor = getColor(this.mainColor || "graphs-blue", theme);
     this.backgroundColor = getColor("body-background", theme);
     this.strokeColor = mainColor;
     this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2);
     this.backgroundGradientEnd = colorUtils.setAlpha(mainColor, 0.2);
     this.selectionBackgroundColor = colorUtils.setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
     this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
     this.maximumLineColor = colorUtils.setAlpha(mainColor, 0.4);
     this.averageLineColor = colorUtils.setAlpha(mainColor, 0.7);
@@ -420,12 +428,88 @@ GraphsController.prototype = {
       if (graph = yield this.isAvailable(graphName)) {
         enabled.push(graph);
       }
     }
     return this._enabledGraphs = enabled;
   }),
 };
 
+/**
+ * A base class for performance graphs to inherit from.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ * @param string metric
+ *        The unit of measurement for this graph.
+ */
+function OptimizationsGraph(parent) {
+  MountainGraphWidget.call(this, parent);
+  this.setTheme();
+}
+
+OptimizationsGraph.prototype = Heritage.extend(MountainGraphWidget.prototype, {
+
+  render: Task.async(function *(threadNode, frameNode) {
+    // Regardless if we draw or clear the graph, wait
+    // until it's ready.
+    yield this.ready();
+
+    if (!threadNode || !frameNode) {
+      this.setData([]);
+      return;
+    }
+
+    let { sampleTimes } = threadNode;
+
+    if (!sampleTimes.length) {
+      this.setData([]);
+      return;
+    }
+
+    // Take startTime/endTime from samples recorded, rather than
+    // using duration directly from threadNode, as the first sample that
+    // equals the startTime does not get recorded.
+    let startTime = sampleTimes[0];
+    let endTime = sampleTimes[sampleTimes.length - 1];
+
+    let bucketSize = (endTime - startTime) / OPTIMIZATIONS_GRAPH_RESOLUTION;
+    let data = createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize);
+
+    // If for some reason we don't have data (like the frameNode doesn't
+    // have optimizations, but it shouldn't be at this point if it doesn't),
+    // log an error.
+    if (!data) {
+      Cu.reportError(`FrameNode#${frameNode.location} does not have optimizations data to render.`);
+      return;
+    }
+
+    this.dataOffsetX = startTime;
+    yield this.setData(data);
+  }),
+
+  /**
+   * Sets the theme via `theme` to either "light" or "dark",
+   * and updates the internal styling to match. Requires a redraw
+   * to see the effects.
+   */
+  setTheme: function (theme) {
+    theme = theme || "light";
+
+    let interpreterColor = getColor("graphs-red", theme);
+    let baselineColor = getColor("graphs-blue", theme);
+    let ionColor = getColor("graphs-green", theme);
+
+    this.format = [
+      { color: interpreterColor },
+      { color: baselineColor },
+      { color: ionColor },
+    ];
+
+    this.backgroundColor = getColor("sidebar-background", theme);
+  }
+});
+
+exports.OptimizationsGraph = OptimizationsGraph;
 exports.FramerateGraph = FramerateGraph;
 exports.MemoryGraph = MemoryGraph;
 exports.TimelineGraph = TimelineGraph;
 exports.GraphsController = GraphsController;
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -28,26 +28,30 @@ loader.lazyRequireGetter(this, "L10N",
 loader.lazyRequireGetter(this, "PerformanceTelemetry",
   "devtools/performance/telemetry", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/performance/markers", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/toolkit/performance/utils");
 loader.lazyRequireGetter(this, "GraphsController",
   "devtools/performance/graphs", true);
+loader.lazyRequireGetter(this, "OptimizationsGraph",
+  "devtools/performance/graphs", true);
 loader.lazyRequireGetter(this, "WaterfallHeader",
   "devtools/performance/waterfall-ticks", true);
 loader.lazyRequireGetter(this, "MarkerView",
   "devtools/performance/marker-view", true);
 loader.lazyRequireGetter(this, "MarkerDetails",
   "devtools/performance/marker-details", true);
 loader.lazyRequireGetter(this, "MarkerUtils",
   "devtools/performance/marker-utils");
 loader.lazyRequireGetter(this, "WaterfallUtils",
   "devtools/performance/waterfall-utils");
+loader.lazyRequireGetter(this, "FrameUtils",
+  "devtools/performance/frame-utils");
 loader.lazyRequireGetter(this, "CallView",
   "devtools/performance/tree-view", true);
 loader.lazyRequireGetter(this, "ThreadNode",
   "devtools/performance/tree-model", true);
 loader.lazyRequireGetter(this, "FrameNode",
   "devtools/performance/tree-model", true);
 loader.lazyRequireGetter(this, "JITOptimizations",
   "devtools/performance/jit", true);
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -72,17 +72,17 @@
                 class="experimental-option"
                 type="checkbox"
                 data-pref="enable-jit-optimizations"
                 label="&performanceUI.enableJITOptimizations;"
                 tooltiptext="&performanceUI.enableJITOptimizations.tooltiptext;"/>
     </menupopup>
   </popupset>
 
-  <hbox class="theme-body" flex="1">
+  <hbox id="body" class="theme-body" flex="1">
 
     <!-- Sidebar: controls and recording list -->
     <vbox id="recordings-pane">
       <toolbar id="recordings-toolbar"
                class="devtools-toolbar">
         <hbox id="recordings-controls"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="main-record-button"
@@ -299,16 +299,17 @@
                   <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
                     <hbox id="jit-optimizations-header">
                       <span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
                       <span class="header-function-name" />
                       <span class="header-file opt-url debugger-link" />
                       <span class="header-line opt-line" />
                     </hbox>
                   </toolbar>
+                  <hbox id="optimizations-graph"></hbox>
                   <vbox id="jit-optimizations-raw-view"></vbox>
                 </vbox>
               </hbox>
 
               <!-- JS FlameChart -->
               <hbox id="js-flamegraph-view" flex="1">
               </hbox>
 
@@ -359,17 +360,16 @@
                          type="function"
                          crop="end"
                          value="&performanceUI.table.function;"/>
                 </hbox>
                 <vbox class="call-tree-cells-container" flex="1"/>
               </vbox>
 
               <!-- Memory FlameChart -->
-              <hbox id="memory-flamegraph-view" flex="1">
-              </hbox>
+              <hbox id="memory-flamegraph-view" flex="1"></hbox>
             </deck>
           </deck>
         </vbox>
       </deck>
     </vbox>
   </hbox>
 </window>
--- a/browser/devtools/performance/test/unit/test_jit-graph-data.js
+++ b/browser/devtools/performance/test/unit/test_jit-graph-data.js
@@ -31,75 +31,88 @@ add_task(function test() {
 
   equal(root.samples, SAMPLE_COUNT / 2, "root has correct amount of samples");
   equal(root.sampleTimes.length, SAMPLE_COUNT / 2, "root has correct amount of sample times");
   // Add time offset since the first sample begins TIME_OFFSET after startTime
   equal(root.sampleTimes[0], startTime + TIME_OFFSET, "root recorded first sample time in scope");
   equal(root.sampleTimes[root.sampleTimes.length - 1], endTime, "root recorded last sample time in scope");
 
   let frame = getFrameNodePath(root, "X");
-  let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, { startTime, endTime, resolution: RESOLUTION });
+  let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, (endTime-startTime)/RESOLUTION);
 
   let TIME_PER_WINDOW = SAMPLE_COUNT / 2 / RESOLUTION * TIME_PER_SAMPLE;
 
-  for (let i = 0; i < 10; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
-    equal(data[i].ys[0], 0.2, "first window has 2 frames in interpreter");
-    equal(data[i].ys[1], 0.2, "first window has 2 frames in baseline");
-    equal(data[i].ys[2], 0.2, "first window has 2 frames in ion");
+  // Filter out the dupes created with the same delta so the graph
+  // can render correctly.
+  let filteredData = [];
+  for (let i = 0; i < data.length; i++) {
+    if (!i || data[i].delta !== data[i-1].delta) {
+      filteredData.push(data[i]);
+    }
+  }
+  data = filteredData;
+
+  for (let i = 0; i < 11; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
+    equal(data[i].values[0], 0.2, "first window has 2 frames in interpreter");
+    equal(data[i].values[1], 0.2, "first window has 2 frames in baseline");
+    equal(data[i].values[2], 0.2, "first window has 2 frames in ion");
   }
-  for (let i = 10; i < 20; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
-    equal(data[i].ys[0], 0, "second window observed no optimizations");
-    equal(data[i].ys[1], 0, "second window observed no optimizations");
-    equal(data[i].ys[2], 0, "second window observed no optimizations");
+  // Start on 11, since i===10 is where the values change, and the new value (0,0,0)
+  // is removed in `filteredData`
+  for (let i = 11; i < 20; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
+    equal(data[i].values[0], 0, "second window observed no optimizations");
+    equal(data[i].values[1], 0, "second window observed no optimizations");
+    equal(data[i].values[2], 0, "second window observed no optimizations");
   }
-  for (let i = 20; i < 30; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
-    equal(data[i].ys[0], 0.3, "third window has 3 frames in interpreter");
-    equal(data[i].ys[1], 0, "third window has 0 frames in baseline");
-    equal(data[i].ys[2], 0, "third window has 0 frames in ion");
+  // Start on 21, since i===20 is where the values change, and the new value (0.3,0,0)
+  // is removed in `filteredData`
+  for (let i = 21; i < 30; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
+    equal(data[i].values[0], 0.3, "third window has 3 frames in interpreter");
+    equal(data[i].values[1], 0, "third window has 0 frames in baseline");
+    equal(data[i].values[2], 0, "third window has 0 frames in ion");
   }
 });
 
 let gUniqueStacks = new RecordingUtils.UniqueStacks();
 
 function uniqStr(s) {
   return gUniqueStacks.getOrAddStringIndex(s);
 }
 
 const TIER_PATTERNS = [
   // 0-99
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 100-199
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 200-299
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 300-399
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 400-499
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
 
   // 500-599
-  // Test current frames in all opts, including that
-  // the same frame with no opts does not get counted
-  ["X", "X", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
+  // Test current frames in all opts
+  ["A", "A", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
 
   // 600-699
   // Nothing for current frame
   ["A", "B", "A", "B", "A", "B", "A", "B", "A", "B"],
 
   // 700-799
   // A few frames where the frame is not the leaf node
   ["X_2 -> Y", "X_2 -> Y", "X_2 -> Y", "X_0", "X_0", "X_0", "A", "A", "A", "A"],
 
   // 800-899
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 900-999
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
 ];
 
 function createSample (i, frames) {
   let sample = {};
   sample.time = i * TIME_PER_SAMPLE;
   sample.frames = [{ location: "(root)" }];
   if (i === 0) {
     return sample;
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -31,16 +31,17 @@ let JsCallTreeView = Heritage.extend(Det
   },
 
   /**
    * Unbinds events.
    */
   destroy: function () {
     OptimizationsListView.destroy();
     this.container = null;
+    this.threadNode = null;
     DetailsSubview.destroy.call(this);
   },
 
   /**
    * Method for handling all the set up for rendering a new call tree.
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
@@ -51,17 +52,17 @@ let JsCallTreeView = Heritage.extend(Det
     let optimizations = recording.getConfiguration().withJITOptimizations;
 
     let options = {
       contentOnly: !PerformanceController.getOption("show-platform-data"),
       invertTree: PerformanceController.getOption("invert-call-tree"),
       flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
       showOptimizationHint: optimizations
     };
-    let threadNode = this._prepareCallTree(profile, interval, options);
+    let threadNode = this.threadNode = this._prepareCallTree(profile, interval, options);
     this._populateCallTree(threadNode, options);
 
     if (optimizations) {
       this.showOptimizations();
     } else {
       this.hideOptimizations();
     }
     OptimizationsListView.reset();
@@ -74,17 +75,17 @@ let JsCallTreeView = Heritage.extend(Det
   },
 
   hideOptimizations: function () {
     $("#jit-optimizations-view").classList.add("hidden");
   },
 
   _onFocus: function (_, treeItem) {
     if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) {
-      OptimizationsListView.setCurrentFrame(treeItem.frame);
+      OptimizationsListView.setCurrentFrame(this.threadNode, treeItem.frame);
       OptimizationsListView.render();
     }
 
     this.emit("focus", treeItem);
   },
 
   /**
    * Fired on the "link" event for the call tree in this container.
--- a/browser/devtools/performance/views/optimizations-list.js
+++ b/browser/devtools/performance/views/optimizations-list.js
@@ -19,66 +19,84 @@ let OptimizationsListView = {
 
   _currentFrame: null,
 
   /**
    * Initialization function called when the tool starts up.
    */
   initialize: function () {
     this.reset = this.reset.bind(this);
+    this._onThemeChanged = this._onThemeChanged.bind(this);
 
     this.el = $("#jit-optimizations-view");
     this.$headerName = $("#jit-optimizations-header .header-function-name");
     this.$headerFile = $("#jit-optimizations-header .header-file");
     this.$headerLine = $("#jit-optimizations-header .header-line");
 
     this.tree = new TreeWidget($("#jit-optimizations-raw-view"), {
       sorted: false,
       emptyText: JIT_EMPTY_TEXT
     });
+    this.graph = new OptimizationsGraph($("#optimizations-graph"));
+    this.graph.setTheme(PerformanceController.getTheme());
 
     // Start the tree by resetting.
     this.reset();
+
+    PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
   },
 
   /**
    * Destruction function called when the tool cleans up.
    */
   destroy: function () {
+    PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
     this.tree = null;
     this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
   },
 
   /**
    * Takes a FrameNode, with corresponding optimization data to be displayed
    * in the view.
    *
    * @param {FrameNode} frameNode
    */
-  setCurrentFrame: function (frameNode) {
+  setCurrentFrame: function (threadNode, frameNode) {
+    if (threadNode !== this.getCurrentThread()) {
+      this._currentThread = threadNode;
+    }
     if (frameNode !== this.getCurrentFrame()) {
       this._currentFrame = frameNode;
     }
   },
 
   /**
    * Returns the current frame node for this view.
    *
    * @return {?FrameNode}
    */
-  getCurrentFrame: function (frameNode) {
+  getCurrentFrame: function () {
     return this._currentFrame;
   },
 
   /**
+   * Returns the current thread node for this view.
+   *
+   * @return {?ThreadNode}
+   */
+  getCurrentThread: function () {
+    return this._currentThread;
+  },
+
+  /**
    * Clears out data in the tree, sets to an empty state,
    * and removes current frame.
    */
   reset: function () {
-    this.setCurrentFrame(null);
+    this.setCurrentFrame(null, null);
     this.clear();
     this.el.classList.add("empty");
     this.emit(EVENTS.OPTIMIZATIONS_RESET);
     this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
   },
 
   /**
    * Clears out data in the tree.
@@ -118,20 +136,29 @@ let OptimizationsListView = {
 
     // An array of sorted OptimizationSites.
     let sites = frameNode.getOptimizations().optimizationSites;
 
     for (let site of sites) {
       this._renderSite(view, site, frameData);
     }
 
+    this._renderTierGraph();
+
     this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
   },
 
   /**
+   * Renders the optimization tier graph over time.
+   */
+  _renderTierGraph: function () {
+    this.graph.render(this.getCurrentThread(), this.getCurrentFrame());
+  },
+
+  /**
    * Creates an entry in the tree widget for an optimization site.
    */
   _renderSite: function (view, site, frameData) {
     let { id, samples, data } = site;
     let { types, attempts } = data;
     let siteNode = this._createSiteNode(frameData, site);
 
     // Cast `id` to a string so TreeWidget doesn't think it does not exist
@@ -170,17 +197,16 @@ let OptimizationsListView = {
         view.add([id, `${id}-types`, `${id}-types-${i}`, { node }]);
       }
     }
   },
 
   /**
    * Creates an element for insertion in the raw view for an OptimizationSite.
    */
-
   _createSiteNode: function (frameData, site) {
     let node = document.createElement("span");
     let desc = document.createElement("span");
     let line = document.createElement("span");
     let column = document.createElement("span");
     let urlNode = this._createDebuggerLinkNode(frameData.url, site.data.line);
 
     let attempts = site.getAttempts();
@@ -220,32 +246,30 @@ let OptimizationsListView = {
 
   /**
    * Creates an element for insertion in the raw view for an IonType.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {IonType} ionType
    * @return {Element}
    */
-
   _createIonNode: function (ionType) {
     let node = document.createElement("span");
     node.textContent = `${ionType.site} : ${ionType.mirType}`;
     node.className = "opt-ion-type";
     return node;
   },
 
   /**
    * Creates an element for insertion in the raw view for an ObservedType.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {ObservedType} type
    * @return {Element}
    */
-
   _createObservedTypeNode: function (type) {
     let node = document.createElement("span");
     let typeNode = document.createElement("span");
 
     typeNode.textContent = `${type.keyedBy}` + (type.name ? ` → ${type.name}` : "");
     typeNode.className = "opt-type";
     node.appendChild(typeNode);
 
@@ -275,17 +299,16 @@ let OptimizationsListView = {
 
   /**
    * Creates an element for insertion in the raw view for an OptimizationAttempt.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {OptimizationAttempt} attempt
    * @return {Element}
    */
-
   _createAttemptNode: function (attempt) {
     let node = document.createElement("span");
     let strategyNode = document.createElement("span");
     let outcomeNode = document.createElement("span");
 
     strategyNode.textContent = attempt.strategy;
     strategyNode.className = "opt-strategy";
     outcomeNode.textContent = attempt.outcome;
@@ -304,17 +327,16 @@ let OptimizationsListView = {
    * Can also optionally pass in an element to modify it rather than
    * creating a new one.
    *
    * @param {String} url
    * @param {Number} line
    * @param {?Element} el
    * @return {Element}
    */
-
   _createDebuggerLinkNode: function (url, line, el) {
     let node = el || document.createElement("span");
     node.className = "opt-url";
     let fileName;
 
     if (this._isLinkableURL(url)) {
       fileName = url.slice(url.lastIndexOf("/") + 1);
       node.classList.add("debugger-link");
@@ -324,17 +346,16 @@ let OptimizationsListView = {
     fileName = fileName || url || "";
     node.textContent = fileName ? `@${fileName}` : "";
     return node;
   },
 
   /**
    * Updates the headers with the current frame's data.
    */
-
   _setHeaders: function (frameData) {
     let isMeta = frameData.isMetaCategory;
     let name = isMeta ? frameData.categoryData.label : frameData.functionName;
     let url = isMeta ? "" : frameData.url;
     let line = isMeta ? "" : frameData.line;
 
     this.$headerName.textContent = name;
     this.$headerLine.textContent = line;
@@ -346,20 +367,27 @@ let OptimizationsListView = {
 
   /**
    * Takes a string and returns a boolean indicating whether or not
    * this is a valid url for linking to the debugger.
    *
    * @param {String} url
    * @return {Boolean}
    */
-
   _isLinkableURL: function (url) {
     return url && url.indexOf &&
        (url.indexOf("http") === 0 ||
         url.indexOf("resource://") === 0 ||
         url.indexOf("file://") === 0);
   },
 
+  /**
+   * Called when `devtools.theme` changes.
+   */
+  _onThemeChanged: function (_, theme) {
+    this.graph.setTheme(theme);
+    this.graph.refresh({ force: true });
+  },
+
   toString: () => "[object OptimizationsListView]"
 };
 
 EventEmitter.decorate(OptimizationsListView);
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -384,16 +384,17 @@ ResponsiveUI.prototype = {
    *    <toolbarbutton class="devtools-responsiveui-home-button" />
    *  </toolbar>
    * </vbox>
    */
   buildUI: function RUI_buildUI() {
     // Toolbar
     this.toolbar = this.chromeDoc.createElement("toolbar");
     this.toolbar.className = "devtools-responsiveui-toolbar";
+    this.toolbar.setAttribute("fullscreentoolbar", "true");
 
     this.menulist = this.chromeDoc.createElement("menulist");
     this.menulist.className = "devtools-responsiveui-menulist";
     this.menulist.setAttribute("editable", "true");
 
     this.menulist.addEventListener("select", this.bound_presetSelected, true);
     this.menulist.addEventListener("change", this.bound_handleManualInput, true);
 
--- a/browser/devtools/shared/widgets/MountainGraphWidget.js
+++ b/browser/devtools/shared/widgets/MountainGraphWidget.js
@@ -7,17 +7,17 @@ const { AbstractCanvasGraph, CanvasGraph
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 // Bar graph constants.
 
 const GRAPH_DAMPEN_VALUES_FACTOR = 0.9;
 
 const GRAPH_BACKGROUND_COLOR = "#ddd";
-const GRAPH_STROKE_WIDTH = 2; // px
+const GRAPH_STROKE_WIDTH = 1; // px
 const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
 const GRAPH_HELPER_LINES_DASH = [5]; // px
 const GRAPH_HELPER_LINES_WIDTH = 1; // px
 
 const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
 const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
@@ -120,18 +120,18 @@ MountainGraphWidget.prototype = Heritage
       throw "The graph format traits are mandatory to style the data source.";
     }
     let { canvas, ctx } = this._getNamedCanvas("mountain-graph-data");
     let width = this._width;
     let height = this._height;
 
     let totalSections = this.format.length;
     let totalTicks = this._data.length;
-    let firstTick = this._data[0].delta;
-    let lastTick = this._data[totalTicks - 1].delta;
+    let firstTick = totalTicks ? this._data[0].delta : 0;
+    let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
 
     let duration = this.dataDuration || lastTick;
     let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
     let dataScaleY = this.dataScaleY = height * this.dampenValuesFactor;
 
     // Draw the graph.
 
     let prevHeights = Array.from({ length: totalTicks }).fill(0);
--- a/browser/docs/DirectoryLinksProvider.rst
+++ b/browser/docs/DirectoryLinksProvider.rst
@@ -189,18 +189,17 @@ A suggested link has additional values:
   triggering set of sites in Firefox.
 - ``check_inadjacency`` - boolean if true prevents the suggested link from being
   shown if the new tab page is showing a site from an inadjacency list.
 - ``explanation`` - string to override the default explanation that appears
   below a Suggested Tile. %1$S is replaced by the triggering adgroup name and
   %2$S is replaced by the triggering site.
 - ``frecent_sites`` - array of strings of the sites that can trigger showing a
   Suggested Tile if the user has the site in one of the top 100 most-frecent
-  pages. Only preapproved array of strings that are hardcoded into the
-  DirectoryLinksProvider module are allowed.
+  pages.
 - ``frequency_caps`` - an object consisting of daily and total frequency caps
   that limit the number of times a Suggested Tile can be shown in the new tab
   per day and overall.
 - ``time_limits`` - an object consisting of start and end timestamps specifying
   when a Suggested Tile may start and has to stop showing in the newtab.
   The timestamp is expected in ISO_8601 format: '2014-01-10T20:00:00.000Z'
 
 The inadjacency list is packaged with Firefox as base64-encoded 1-way-hashed
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -59,188 +59,16 @@ const PREF_SELECTED_LOCALE = "general.us
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
-// Only allow explicitly approved frecent sites with display name
-const ALLOWED_FRECENT_SITES = new Map([
-  [ '1800petmeds.com,800petmeds.com,adopt.dogtime.com,adoptapet.com,akc.org,americanhumane.org,animal.discovery.com,animalconcerns.org,animalshelter.org,arcatapet.com,aspca.org,avma.org,bestfriends.org,blog.petmeds.com,buddydoghs.com,carealotpets.com,dailypuppy.com,dog.com,dogbar.com,dogbreedinfo.com,drsfostersmith.com,entirelypets.com,farmsanctuary.org,farmusa.org,freekibble.com,freekibblekat.com,healthypets.com,hsus.org,humanesociety.org,liveaquaria.com,marinedepot.com,medi-vet.com,nationalpetpharmacy.com,nsalamerica.org,nycacc.org,ohmydogsupplies.com,pet-dog-cat-supply-store.com,petcarerx.com,petco.com,petdiscounters.com,petedge.com,peteducation.com,petfinder.com,petfooddirect.com,petguys.com,petharbor.com,petmountain.com,petplanet.co.uk,pets911.com,petsmart.com,petsuppliesplus.com,puppyfind.com,revivalanimal.com,terrificpets.com,thatpetplace.com,theanimalrescuesite.com,theanimalrescuesite.greatergood.com,thefluffingtonpost.com,therainforestsite.com,vetdepot.com',
-    'pet' ],
-  [ '1aauto.com,autoblog.com,autoguide.com,autosite.com,autoweek.com,bimmerpost.com,bmwblog.com,boldride.com,caranddriver.com,carcomplaints.com,carspoon.com,cherokeeforum.com,classiccars.com,commercialtrucktrader.com,corvetteforum.com,dealerrater.com,ebizautos.com,ford-trucks.com,hemmings.com,jalopnik.com,jeepforum.com,jeepsunlimited.com,jk-forum.com,legendaryspeed.com,motorauthority.com,motortrend.com,motorwings.com,odometer.com,pirate4x4.com,purecars.com,roadandtrack.com,teslamotorsclub.com,topgear.com,topspeed.com,totalmini.com,truckpaper.com,wranglerforum.com',
-    'auto' ],
-  [ 'autobytel.com,autocheck.com,automotive.com,autonation.com,autos.aol.com,autos.msn.com,autos.yahoo.com,autotrader.autos.msn.com,autotrader.com,autotraderclassics.com,autoweb.com,car.com,carbuyingtips.com,carfax.com,cargurus.com,carmax.com,carprices.com,cars.com,cars.oodle.com,carsdirect.com,carsforsale.com,edmunds.com,hertzcarsales.com,imotors.com,intellichoice.com,internetautoguide.com,kbb.com,lemonfree.com,nada.com,nadaguides.com,thecarconnection.com,thetruthaboutcars.com,truecar.com,usedcars.com,usnews.rankingsandreviews.com',
-    'auto' ],
-  [ 'acura.com,audi.ca,audi.com,audiusa.com,automobiles.honda.com,bentleymotors.com,bmw.com,bmwusa.com,buick.com,buyatoyota.com,cadillac.com,cars.mclaren.com,chevrolet.com,choosenissan.com,chrysler.com,daimler.com,dodge.com,ferrari.com/en_us,fiskerautomotive.com,ford.com,gm.com,gmc.com,hummer.com,hyundai.com,hyundaiusa.com,infiniti.com,infinitiusa.com,jaguarusa.com,jeep.com,kia.com,kiamotors.com,lamborghini.com/en/home,landrover.com,landroverusa.com,lexus.com,lincoln.com,maserati.us,mazda.com,mazdausa.com,mbusa.com,mbusi.com,mercedes-amg.com,mercedes-benz.com,mercuryvehicles.com,miniusa.com,nissanusa.com,pontiac.com,porsche.com/usa,ramtrucks.com,rolls-roycemotorcars.com,saturn.com,scion.com,subaru.com,teslamotors.com,toyota.com,volkswagen.co.uk,volkswagen.com,volvocars.com/us,vw.com',
-    'auto' ],
-  [ '1010tires.com,4wheelparts.com,advanceautoparts.com,andysautosport.com,autoanything.com,autogeek.net,autopartsgiant.com,autopartswarehouse.com,autotrucktoys.com,autozone.com,autozoneinc.com,bavauto.com,bigotires.com,bilsteinus.com,brembo.com,car-part.com,carid.com,carparts.com,carquest.com,dinancars.com,discounttire.com,discounttiredirect.com,firestonecompleteautocare.com,goodyear.com,hrewheels,jcwhitney.com,kw-suspensions.com,momousa.com,napaonline.com,onlinetires.com,oreillyauto.com,oriellysautoparts.com,pepboys.com,repairpal.com,rockauto.com,shop.advanceautoparts.com,slickcar.com,stoptech.com,streetbeatcustoms.com,summitracing.com,tirebuyer.com,tirerack.com,tiresplus.com,tsw.com,velocitymotoring.com,wheelmax.com',
-    'auto parts' ],
-  [ 'abebooks.co.uk,abebooks.com,addall.com,alibris.com,allaboutcircuits.com,allbookstores.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biblio.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,goodreads.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,usedbooksearch.co.uk,valorebooks.com,valuemags.com,vialibri.net,wwnorton.com,zoobooks.com',
-    'literature' ],
-  [ '53.com,ally.com,bankofamerica.com,bbt.com,bnymellon.com,capitalone.com/bank/,chase.com,citi.com,citibank.com,citizensbank.com,citizensbankonline.com,creditonebank.com,everbank.com,hsbc.com,key.com,pnc.com,pncbank.com,rbs.co.uk,regions.com,sovereignbank.com,suntrust.com,tdbank.com,usaa.com,usbank.com,wachovia.com,wamu.com,wellsfargo.com,wsecu.org',
-    'banking' ],
-  [ '247wallst.com,bizjournals.com,bloomberg.com,businessweek.com,cnbc.com,cnnmoney.com,dowjones.com,easyhomesite.com,economist.com,entrepreneur.com,fastcompany.com,finance.yahoo.com,forbes.com,fortune.com,foxbusiness.com,ft.com,hbr.org,ibtimes.com,inc.com,manta.com,marketwatch.com,newsweek.com,online.wsj.com,qz.com,reuters.com,smartmoney.com,wsj.com',
-    'business news' ],
-  [ 'achievecard.com,americanexpress.com,barclaycardus.com,card.com,citicards.com,comparecards.com,creditcards.citi.com,discover.com,discovercard.com,experian.com,skylightpaycard.com,squareup.com,visa.com,visabuxx.com,visaextras.com',
-    'finance' ],
-  [ 'alliantcreditunion.org,connexuscu.org,lmcu.org,nasafcu.com,navyfcu.org,navyfederal.org,penfed.org,sccu.com,suncoastcreditunion.com,tinkerfcu.org,veridiancu.org',
-    'finance' ],
-  [ 'allbusiness.com,bankrate.com,buyersellertips.com,cboe.com,cnbcprime.com,coindesk.com,dailyfinance.com,dailyfx.com,dealbreaker.com,easierstreetdaily.com,economywatch.com,etfdailynews.com,etfdb.com,financeformulas.net,finviz.com,fool.com,forexpros.com,forexthreads.com,ftpress.com,fx-exchange.com,insidermonkey.com,investmentu.com,investopedia.com,investorjunkie.com,investors.com,kiplinger.com,minyanville.com,moneymorning.com,moneyning.com,moneysavingexpert.com,morningstar.com,nakedcapitalism.com,ncsoft.net,oilprice.com,realclearmarkets.com,rttnews.com,seekingalpha.com,silverdoctors.com,stockcharts.com,stockpickr.com,thefinancials.com,thestreet.com,wallstreetinsanity.com,wikinvest.com,xe.com,youngmoney.com',
-    'investing' ],
-  [ 'edwardjones.com,fidelity.com,goldmansachs.com,jpmorgan.com,ml.com,morganstanley.com,mymerrill.com,personal.vanguard.com,principal.com,schwab.com,schwabplan.com,scottrade.com,tdameritrade.com,troweprice.com,vanguard.com',
-    'investing' ],
-  [ '247lendinggroup.com,americanoneunsecured.com,avant.com,bestegg.com,chasestudentloans.com,eloan.com,gofundme.com,guidetolenders.com,kiva.org,lendacademy.com,lendingclub.com,lendingtree.com,lightstream.com,loanio.com,manageyourloans.com,meetearnest.com,microplace.com,netcredit.com,peer-lend.com,personalloans.com,prosper.com,salliemae.com,sofi.com,springleaf.com,uk.zopa.com,upstart.com',
-    'finance' ],
-  [ 'betterment.com,blooom.com,futureadvisor.com,kapitall.com,motifinvesting.com,personalcapital.com,wealthfront.com,wisebanyan.com',
-    'investing' ],
-  [ 'bancdebinary.com,cherrytrade.com,empireoption.net,etrade.com,firstrade.com,forex.com,interactivebrokers.com,ishares.com,optionsxpress.com,sharebuilder.com,thinkorswim.com,tradeking.com,trademonster.com,us.etrade.com,zecco.com',
-    'finance' ],
-  [ 'annualcreditreport.com,bluebird.com,credio.com,creditkarma.com,creditreport.com,cybersource.com,equifax.com,freecreditreport.com,freecreditscore.com,freedomdebtrelief.com,freescoreonline.com,mint.com,moneymappress.com,myfico.com,nationaldebtrelief.com,onesmartpenny.com,paypal.com,transunion.com,truecredit.com,upromise.com,vuebill.com,xpressbillpay.com,youneedabudget.com',
-    'personal finance' ],
-  [ 'angieslist.com,bloomberg.com,businessinsider.com,buydomains.com,domain.com,entrepreneur.com,fastcompany.com,forbes.com,fortune.com,godaddy.com,inc.com,manta.com,nytimes.com,openforum.com,register.com,salesforce.com,sba.gov,sbomag.com,shopsmall.americanexpress.com,smallbusiness.yahoo.com,squarespace.com,startupjournal.com,startupnation.com,weebly.com,wordpress.com,youngentrepreneur.com',
-    'business news' ],
-  [ '1040now.net,24hourtax.com,acttax.com,comparetaxsoftware.org,e-file.com,etax.com,free1040taxreturn.com,hrblock.com,intuit.com,irstaxdoctors.com,libertytax.com,octaxcol.com,pay1040.com,priortax.com,quickbooks.com,quickrefunds.com,rapidtax.com,refundschedule.com,taxact.com,taxactonline.com,taxefile.com,taxhead.com,taxhelptoday.me,taxsimple.org,turbotax.com',
-    'tax' ],
-  [ 'adeccousa.com,americasjobexchange.com,aoljobs.com,applicantpro.com,applicantstack.com,apply-4-jobs.com,apply2jobs.com,att.jobs,beyond.com,careerboutique.com,careerbuilder.com,careerflash.net,careerslocal.net,climber.com,coverlettersandresume.com,dice.com,diversityonecareers.com,employmentguide.com,everyjobforme.com,experteer.com,find.ly,findtherightjob.com,freelancer.com,gigats.com,glassdoor.com,governmentjobs.com,hrapply.com,hrdepartment.com,hrsmart.com,ihire.com,indeed.com,internships.com,itsmycareer.com,job-applications.com,job-hunt.org,job-interview-site.com,job.com,jobcentral.com,jobdiagnosis.com,jobhat.com,jobing.com,jobrapido.com,jobs.aol.com,jobs.net,jobsbucket.com,jobsflag.com,jobsgalore.com,jobsonline.com,jobsradar.com,jobster.com,jobtorch.com,jobungo.com,jobvite.com,juju.com,linkedin.com,livecareer.com,localjobster.com,mindtools.com,monster.com,myjobhelper.com,myperfectresume.com,payscale.com,pryor.com,quintcareers.com,randstad.com,recruitingcenter.net,resume-library.com,resume-now.com,roberthalf.com,salary.com,salaryexpert.com,simplyhired.com,smartrecruiters.com,snagajob.com,startwire.com,theladders.com,themuse.com,theresumator.com,thingamajob.com,usajobs.gov,ziprecruiter.com',
-    'career services' ],
-  [ 'americanheart.org,americanredcross.com,americares.org,catholiccharitiesusa.org,charitybuzz.com,charitynavigator.org,charitywater.org,directrelief.org,fao.org,habitat.org,hrw.org,imf.org,mskcc.org,ohchr.org,redcross.org,reliefweb.int,salvationarmyusa.org,savethechildren.org,un.org,undp.org,unep.org,unesco.org,unfpa.org,unhcr.org,unicef.org,unicefusa.org,unops.org,volunteermatch.org,wfp.org,who.int,worldbank.org',
-    'philanthropic' ],
-  [ 'academia.edu,albany.edu,american.edu,amity.edu,annauniv.edu,apus.edu,arizona.edu,ashford.edu,asu.edu,auburn.edu,austincc.edu,baylor.edu,bc.edu,berkeley.edu,brandeis.edu,brookings.edu,brown.edu,bu.edu,buffalo.edu,byu.edu,calpoly.edu,calstate.edu,caltech.edu,cam.ac.uk,cambridge.org,capella.edu,case.edu,clemson.edu,cmu.edu,colorado.edu,colostate-pueblo.edu,colostate.edu,columbia.edu,commnet.edu,cornell.edu,cpp.edu,csulb.edu,csun.edu,csus.edu,cuny.edu,cwru.edu,dartmouth.edu,depaul.edu,devry.edu,drexel.edu,du.edu,duke.edu,emory.edu,fau.edu,fcps.edu,fiu.edu,fordham.edu,fsu.edu,fullerton.edu,fullsail.edu,gatech.edu,gcu.edu,georgetown.edu,gmu.edu,gsu.edu,gwu.edu,harvard.edu,hawaii.edu,hbs.edu,iastate.edu,iit.edu,illinois.edu,indiana.edu,iu.edu,jhu.edu,k-state.edu,kent.edu,ku.edu,lamar.edu,liberty.edu,losrios.edu,lsu.edu,luc.edu,maine.edu,maricopa.edu,mass.edu,miami.edu,miamioh.edu,missouri.edu,mit.edu,mnscu.edu,monash.edu,msu.edu,mtu.edu,nau.edu,ncsu.edu,nd.edu,neu.edu,njit.edu,northeastern.edu,northwestern.edu,nova.edu,nyu.edu,odu.edu,ohio-state.edu,ohio.edu,okstate.edu,oregonstate.edu,osu.edu,ou.edu,ox.ac.uk,pdx.edu,pearson.com,phoenix.edu,pitt.edu,princeton.edu,psu.edu,purdue.edu,regis.edu,rice.edu,rit.edu,rochester.edu,rpi.edu,rutgers.edu,sc.edu,scu.edu,sdsu.edu,seattleu.edu,sfsu.edu,si.edu,sjsu.edu,snhu.edu,stanford.edu,stonybrook.edu,suny.edu,syr.edu,tamu.edu,temple.edu,towson.edu,ttu.edu,tufts.edu,ua.edu,uark.edu,ub.edu,uc.edu,uccs.edu,ucdavis.edu,ucf.edu,uchicago.edu,uci.edu,ucla.edu,uconn.edu,ucr.edu,ucsb.edu,ucsc.edu,ucsd.edu,ucsf.edu,udel.edu,udemy.com,ufl.edu,uga.edu,uh.edu,uic.edu,uillinois.edu,uiowa.edu,uiuc.edu,uky.edu,umass.edu,umb.edu,umbc.edu,umd.edu,umich.edu,umn.edu,umuc.edu,unc.edu,uncc.edu,unf.edu,uniminuto.edu,universityofcalifornia.edu,unl.edu,unlv.edu,unm.edu,unt.edu,uoc.edu,uoregon.edu,upc.edu,upenn.edu,upi.edu,uri.edu,usc.edu,usf.edu,usg.edu,usu.edu,uta.edu,utah.edu,utdallas.edu,utexas.edu,utk.edu,uvm.edu,uw.edu,uwm.edu,vanderbilt.edu,vccs.edu,vcu.edu,virginia.edu,vt.edu,waldenu.edu,washington.edu,wayne.edu,wednet.edu,wgu.edu,wisc.edu,wisconsin.edu,wm.edu,wmich.edu,wsu.edu,wustl.edu,wvu.edu,yale.edu',
-    'college' ],
-  [ 'collegeboard.com,collegeconfidential.com,collegeview.com,ecollege.com,finaid.org,find-colleges-now.com,ratemyprofessors.com,ratemyteachers.com,studentsreview.com',
-    'college' ],
-  [ 'actstudent.org,adaptedmind.com,aesoponline.com,archives.com,bibme.org,blackboard.com,bookrags.com,cengage.com,chegg.com,classdojo.com,classzone.com,cliffsnotes.com,coursecompass.com,educationconnection.com,educationdynamics.com,ets.org,familysearch.org,fastweb.com,genealogy.com,gradesaver.com,instructure.com,khanacademy.org,learn4good.com,mathway.com,mathxl.com,mcgraw-hill.com,merriam-webster.com,mheducation.com,niche.com,openstudy.com,pearsoned.com,pearsonmylabandmastering.com,pearsonsuccessnet.com,poptropica.com,powerschool.com,proprofs.com,purplemath.com,quizlet.com,readwritethink.org,renlearn.com,rhymezone.com,schoolloop.com,schoology.com,smithsonianmag.com,sparknotes.com,study.com,studyisland.com,studymode.com,synonym.com,teacherprobs.com,teacherspayteachers.com,tutorvista.com,vocabulary.com,yourschoolmatch.com',
-    'education' ],
-  [ 'browardschools.com,k12.ca.us,k12.fl.us,k12.ga.us,k12.in.us,k12.mn.us,k12.mo.us,k12.nc.us,k12.nj.us,k12.oh.us,k12.va.us,k12.wi.us',
-    'education' ],
-  [ 'coolmath-games.com,coolmath.com,coolmath4kids.com,coolquiz.com,funbrain.com,funtrivia.com,gamesforthebrain.com,girlsgogames.com,hoodamath.com,lumosity.com,math.com,mathsisfun.com,trivia.com,wizard101.com',
-    'learning games' ],
-  [ 'askmen.com,boredomtherapy.com,buzzfeed.com,complex.com,dailymotion.com,elitedaily.com,gawker.com,howstuffworks.com,instagram.com,madamenoire.com,polygon.com,ranker.com,rollingstone.com,ted.com,theblaze.com,thechive.com,thecrux.com,thedailybeast.com,thoughtcatalog.com,uproxx.com,upworthy.com,zergnet.com',
-    'entertainment' ],
-  [ '11points.com,7gid.com,adultswim.com,break.com,cheezburger.com,collegehumor.com,cracked.com,dailydawdle.com,damnlol.com,dumb.com,dumblaws.com,ebaumsworld.com,explosm.net,failblog.org,fun-gallery.com,funnygig.com,funnyjunk.com,funnymama.com,funnyordie.com,funnytear.com,funplus.com,glassgiant.com,goingviralposts.com,gorillamask.net,i-am-bored.com,icanhascheezburger.com,ifunny.com,imjussayin.co,inherentlyfunny.com,izismile.com,jokes.com,keenspot.com,knowyourmeme.com,laughstub.com,memebase.com,mememaker.net,metacafe.com,mylol.com,picslist.com,punoftheday.com,queendom.com,rajnikantvscidjokes.in,regretfulmorning.com,shareonfb.com,somethingawful.com,stupidvideos.com,superfunnyimages.com,thedailywh.at,theonion.com,tosh.comedycentral.com,uberhumor.com,welltimedphotos.com',
-    'humor' ],
-  [ 'air.tv,amctheatres.com,boxofficemojo.com,cinapalace.com,cinaplay.com,cinemablend.com,cinemark.com,cinematical.com,collider.com,comicbookmovie.com,comingsoon.net,crackle.com,denofgeek.us,dreamworks.com,empireonline.com,enstarz.com,fandango.com,filmschoolrejects.com,flickeringmyth.com,flixster.com,fullmovie2k.com,g2g.fm,galleryhip.com,hollywood.com,hollywoodreporter.com,iglomovies.com,imdb.com,indiewire.com,instantwatcher.com,joblo.com,kickass.to,kissdrama.net,marcustheatres.com,megashare9.com,moviefone.com,movieinsider.com,moviemistakes.com,moviepilot.com,movierulz.com,movies.com,movies.yahoo.com,movieseum.com,movietickets.com,movieweb.com,mrmovietimes.com,mymovieshub.com,netflix.com,onlinemovies.pro,pelis24.com,projectfreetv.ch,redbox.com,regmovies.com,repelis.tv,rogerebert.suntimes.com,ropeofsilicon.com,rottentomatoes.com,sidereel.com,slashfilm.com,solarmovie.is,starwars.com,superherohype.com,tcm.com,twomovies.us,variety.com,vimeo.com,viooz.ac,warnerbros.com,watchfree.to,wbredirect.com,youtubeonfire.com,zmovie.tw,zumvo.com',
-    'movie' ],
-  [ '1079ishot.com,2dopeboyz.com,8tracks.com,acdc.com,allaccess.com,allhiphop.com,allmusic.com,audiofanzine.com,audiomack.com,azlyrics.com,baeblemusic.com,bandsintown.com,billboard.com,brooklynvegan.com,brunomars.com,buzznet.com,cmt.com,coachella.com,consequenceofsound.net,contactmusic.com,countryweekly.com,dangerousminds.net,datpiff.com,ddotomen.com,diffuser.fm,directlyrics.com,djbooth.net,eventful.com,fireflyfestival.com,genius.com,guitartricks.com,harmony-central.com,hiphopdx.com,hiphopearly.com,hypem.com,idolator.com,iheart.com,jambase.com,kanyetothe.com,knue.com,lamusica.com,last.fm,livemixtapes.com,loudwire.com,lyricinterpretations.com,lyrics.net,lyricsbox.com,lyricsmania.com,lyricsmode.com,metal-archives.com,metrolyrics.com,mp3.com,mtv.co.uk,myspace.com,newnownext.com,noisetrade.com,okayplayer.com,pandora.com,phish.com,pigeonsandplanes.com,pitchfork.com,popcrush.com,radio.com,rap-up.com,rdio.com,reverbnation.com,revolvermag.com,rockhall.com,saavn.com,songlyrics.com,soundcloud.com,spin.com,spinrilla.com,spotify.com,stereogum.com,stereotude.com,talkbass.com,tasteofcountry.com,thebacklot.com,theboombox.com,theboot.com,thissongissick.com,tunesbaby.com,ultimate-guitar.com,ultimateclassicrock.com,vevo.com,vibe.com,vladtv.com,whosampled.com,wikibit.me,worldstarhiphop.com,wyrk.com,xxlmag.com',
-    'music' ],
-  [ 'aceshowbiz.com,aintitcoolnews.com,allkpop.com,askkissy.com,atraf.co.il,audioboom.com,beamly.com,beyondhollywood.com,blastr.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,buzzsugar.com,cambio.com,celebdirtylaundry.com,celebrity-gossip.net,celebuzz.com,chisms.net,comicsalliance.com,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,dlisted.com,egotastic.com,empirenews.net,enelbrasero.com,eonline.com,etonline.com,ew.com,extratv.com,facade.com,famousfix.com,fanaru.com,fanpop.com,fansshare.com,fhm.com,geektyrant.com,glamourpage.com,gossipcenter.com,gossipcop.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodlife.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.buzznet.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,laineygossip.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,nationalenquirer.com,necolebitchie.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,okmagazine.com,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popsugar.com,purepeople.com,radaronline.com,rantchic.com,realitytea.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screencrush.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,socialitelife.com,sourcefednews.com,splitsider.com,starcasm.net,starmagazine.com,starpulse.com,straightfromthea.com,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,thehollywoodgossip.com,theresacaputo.com,thesuperficial.com,thezooom.com,tmz.com,tvnotas.com.mx,twanatells.com,usmagazine.com,vanityfair.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com',
-    'entertainment news' ],
-  [ 'abc.go.com,abcfamily.go.com,abclocal.go.com,accesshollywood.com,aetv.com,amctv.com,animalplanet.com,bbcamerica.com,bet.com,biography.com,bravotv.com,cartoonnetwork.com,cbn.com,cbs.com,cc.com,centrictv.com,cinemax.com,comedycentral.com,ctv.ca,cwtv.com,daytondailynews.com,drphil.com,dsc.discovery.com,fox.com,fox23.com,fox4news.com,fxnetworks.com,hbo.com,history.com,hulu.com,ifc.com,iqiyi.com,jeopardy.com,kfor.com,logotv.com,mtv.com,myfoxchicago.com,myfoxdc.com,myfoxmemphis.com,myfoxphilly.com,nbc.com,nbcchicago.com,oxygen.com,pbs.org,pbskids.org,rachaelrayshow.com,rtve.es,scifi.com,sho.com,showtimeanytime.com,spike.com,sundance.tv,syfy.com,tbs.com,teamcoco.com,telemundo.com,thedoctorstv.com,titantv.com,tlc.com,tlc.discovery.com,tnt.tv,tntdrama.com,tv.com,tvguide.com,tvseriesfinale.com,usanetwork.com,uvidi.com,vh1.com,viki.com,watchcartoononline.com,watchseries-online.ch,wetv.com,wheeloffortune.com,whio.com,wnep.com,wral.com,wtvr.com,xfinitytv.com,yidio.com',
-    'TV show' ],
-  [ 'americanhiking.org,appalachiantrail.org,canadiangeographic.ca,defenders.org,discovermagazine.com,discoveroutdoors.com,dsc.discovery.com,earthtouchnews.com,edf.org,epa.gov,ewg.org,fishngame.org,foe.org,fs.fed.us,geography.about.com,landtrustalliance.org,nationalgeographic.com,nature.com,nrdc.org,nwf.org,outdoorchannel.com,outdoors.org,seedmagazine.com,trcp.org,usda.gov,worldwildlife.org',
-    'environment' ],
-  [ 'abbreviations.com,abcmouse.com,abcya.com,achieve3000.com,ancestry.com,animaljam.com,babble.com,babycenter.com,babynamespedia.com,behindthename.com,bestmomstv.com,brainyquote.com,cafemom.com,citationmachine.net,clubpenguin.com,cutemunchkins.com,discoveryeducation.com,disney.com,easybib.com,education.com,enotes.com,everydayfamily.com,familyeducation.com,gamefaqs.com,greatschools.org,hrw.com,imvu.com,infoplease.com,itsybitsysteps.com,justmommies.com,k12.com,kidsactivitiesblog.com,mathwarehouse.com,mom.me,mom365.com,mommyshorts.com,momswhothink.com,momtastic.com,monsterhigh.com,myheritage.com,nameberry.com,nickmom.com,pampers.com,parenthood.com,parenting.com,parenting.ivillage.com,parents.com,parentsociety.com,raz-kids.com,regentsprep.org,scarymommy.com,scholastic.com,shmoop.com,softschools.com,spanishdict.com,starfall.com,thebump.com,thefreedictionary.com,thenest.com,thinkbabynames.com,todaysparent.com,webkinz.com,whattoexpect.com',
-    'family' ],
-  [ 'americangirl.com,barbie.com,barbiecollectibles.com,cartoonnetworkshop.com,chuckecheese.com,coloring.ws,disney.co.uk,disney.com.au,disney.go.com,disney.in,disneychannel-asia.com,disneyinternational.com,disneyjunior.com,disneylatino.com,disneyme.com,dltk-kids.com,dressupone.com,fantage.com,funbrainjr.com,hotwheels.com,icarly.com,kiwicrate.com,marvel.com,marvelkids.com,mattelgames.com,maxsteel.com,monkeyquest.com,nick-asia.com,nick.co.uk,nick.com,nick.tv,nickelodeon.com.au,nickjr.co.uk,nickjr.com,ninjakiwi.com,notdoppler.com,powerrangers.com,sciencekids.co.nz,search.disney.com,seventeen.com,teennick.com,theslap.com,yepi.com',
-    'family' ],
-  [ 'alabama.gov,archives.gov,bls.gov,ca.gov,cancer.gov,cdc.gov,census.gov,cia.gov,cms.gov,commerce.gov,ct.gov,delaware.gov,dhs.gov,doi.gov,dol.gov,dot.gov,ed.gov,eftps.gov,epa.gov,fbi.gov,fda.gov,fema.gov,flhsmv.gov,ftc.gov,ga.gov,georgia.gov,gpo.gov,hhs.gov,house.gov,hud.gov,illinois.gov,in.gov,irs.gov,justice.gov,ky.gov,loc.gov,louisiana.gov,maryland.gov,mass.gov,michigan.gov,mo.gov,nih.gov,nj.gov,nps.gov,ny.gov,nyc.gov,ohio.gov,ok.gov,opm.gov,oregon.gov,pa.gov,recreation.gov,sba.gov,sc.gov,sec.gov,senate.gov,state.fl.us,state.gov,state.il.us,state.ma.us,state.mi.us,state.mn.us,state.nc.us,state.ny.us,state.oh.us,state.pa.us,studentloans.gov,telldc.com,texas.gov,tn.gov,travel.state.gov,tsa.gov,usa.gov,uscis.gov,uscourts.gov,usda.gov,usdoj.gov,usembassy.gov,usgs.gov,utah.gov,va.gov,virginia.gov,wa.gov,whitehouse.gov,wi.gov,wisconsin.gov',
-    'government' ],
-  [ 'beachbody.com,bodybuilding.com,caloriecount.com,extremefitness.com,fitbit.com,fitday.com,fitnessmagazine.com,fitnessonline.com,fitwatch.com,livestrong.com,maxworkouts.com,mensfitness.com,menshealth.com,muscleandfitness.com,muscleandfitnesshers.com,myfitnesspal.com,shape.com,womenshealthmag.com',
-    'health & fitness' ],
-  [ 'activebeat.com,alliancehealth.com,beyonddiet.com,caring.com,complete-health-and-happiness.com,diabeticconnect.com,doctoroz.com,everydayhealth.com,followmyhealth.com,greatist.com,health.com,healthboards.com,healthcaresource.com,healthgrades.com,healthguru.com,healthination.com,healthtap.com,helpguide.org,iherb.com,kidshealth.org,lifescript.com,lovelivehealth.com,medicaldaily.com,mercola.com,perfectorigins.com,prevention.com,qualityhealth.com,questdiagnostics.com,realself.com,sharecare.com,sparkpeople.com,spryliving.com,steadyhealth.com,symptomfind.com,ucomparehealthcare.com,vitals.com,webmd.com,weightwatchers.com,wellness.com,zocdoc.com',
-    'health & wellness' ],
-  [ 'aetna.com,anthem.com,athenahealth.com,bcbs.com,bluecrossca.com,cigna.benefitnation.net,cigna.com,cigna.healthplan.com,ehealthcare.com,ehealthinsurance.com,empireblue.com,goldenrule.com,healthcare.gov,healthnet.com,humana-medicare.com,humana.com,kaiserpermanente.org,metlife.com,my.cigna.com,mybenefits.metlife.com,myuhc.com,uhc.com,unitedhealthcareonline.com,walterrayholt.com',
-    'health insurance' ],
-  [ 'aafp.org,americanheart.org,apa.org,cancer.org,cancercenter.com,caremark.com,clevelandclinic.org,diabetesfree.org,drugs.com,emedicinehealth.com,express-scripts.com,familydoctor.org,goodrx.com,healthcaremagic.com,healthfinder.gov,healthline.com,ieee.org,intelihealth.com,labcorp.com,livecellresearch.com,mayoclinic.com,mayoclinic.org,md.com,medcohealth.com,medhelp.org,medicalnewstoday.com,medicare.gov,medicaresupplement.com,medicinenet.com,medscape.com,memorialhermann.org,merckmanuals.com,patient.co.uk,psychcentral.com,psychology.org,psychologytoday.com,rightdiagnosis.com,rxlist.com,socialpsychology.org,spine-health.com,who.int',
-    'health & wellness' ],
-  [ 'aaa.com,aig.com,allianz-assistance.com,allstate.com,allstateagencies.com,amfam.com,amica.com,autoquotesdirect.com,esurance.com,farmers.com,farmersagent.com,geico.com,general-car-insurance-quotes.net,insurance.com,libertymutual.com,libertymutualgroup.com,mercuryinsurance.com,nationwide.com,progressive.com,progressiveagent.com,progressiveinsurance.com,provide-insurance.com,safeco.com,statefarm.com,thehartford.com,travelers.com,usaa.com',
-    'insurance' ],
-  [ '101cookbooks.com,allrecipes.com,bettycrocker.com,bonappetit.com,chocolateandzucchini.com,chow.com,chowhound.chow.com,cookinglight.com,cooks.com,cooksillustrated.com,cooksrecipes.com,delish.com,eater.com,eatingwell.com,epicurious.com,food.com,foodandwine.com,foodgawker.com,foodnetwork.com,gourmet.com,grouprecipes.com,homemaderecipes.co,iheartnaptime.net,kraftfoods.com,kraftrecipes.com,myrecipes.com,opentable.com,pillsbury.com,recipe.com,recipesource.com,recipezaar.com,saveur.com,seriouseats.com,simplyrecipes.com,smittenkitchen.com,southernliving.com,supercook.com,tasteofhome.com,tastespotting.com,technicpack.net,thekitchn.com,urbanspoon.com,wonderhowto.com,yelp.com,yummly.com,zagat.com',
-    'food & lifestyle' ],
-  [ 'aarp.org,allure.com,bustle.com,cosmopolitan.com,diply.com,eharmony.com,elle.com,glamour.com,grandascent.com,harpersbazaar.com,hellogiggles.com,instructables.com,instyle.com,marieclaire.com,match.com,mindbodygreen.com,nymag.com,okcupid.com,petco.com,photobucket.com,pof.com,rantlifestyle.com,redbookmag.com,reddit.com,sheknows.com,style.com,stylebistro.com,theilovedogssite.com,theknot.com,thescene.com,thrillist.com,vogue.com,womansday.com,youngcons.com,yourdictionary.com',
-    'lifestyle' ],
-  [ 'apartmentratings.com,apartmenttherapy.com,architectmagazine.com,architecturaldigest.com,askthebuilder.com,bhg.com,bobvila.com,countryhome.com,countryliving.com,davesgarden.com,decor8blog.com,decorpad.com,diycozyhome.com,diyideas.com,diynetwork.com,doityourself.com,domainehome.com,dwell.com,elledecor.com,familyhandyman.com,frontdoor.com,gardenguides.com,gardenweb.com,getdecorating.com,goodhousekeeping.com,hgtv.com,hgtvgardens.com,hobbylobby.com,homeadvisor.com,homerepair.about.com,hometalk.com,hometime.com,hometips.com,housebeautiful.com,houzz.com,inhabitat.com,lonny.com,makingitlovely.com,marthastewart.com,michaels.com,myhomeideas.com,realsimple.com,remodelista.com,shanty-2-chic.com,styleathome.com,thehandmadehome.net,thehealthyhomeeconomist.com,thisoldhouse.com,traditionalhome.com,trulia.com,younghouselove.com',
-    'home & lifestyle' ],
-  [ '10best.com,10tv.com,11alive.com,19actionnews.com,9news.com,abcnews.com,abcnews.go.com,adweek.com,ajc.com,anchorfree.us,arcamax.com,austin360.com,azcentral.com,bbc.co.uk,boston.com,bostonglobe.com,capecodonline.com,cbsnews.com,cheatsheet.com,chicagotribune.com,chron.com,citylab.com,cnn.com,csmonitor.com,dailyitem.com,dailymail.co.uk,dallasnews.com,eleconomista.es,examiner.com,fastcolabs.com,fivethirtyeight.com,foursquare.com,foxcarolina.com,foxnews.com,globalnews.ca,greatergood.com,guardian.co.uk,historynet.com,huffingtonpost.co.uk,huffingtonpost.com,ijreview.com,independent.co.uk,journal-news.com,kare11.com,kcra.com,kctv5.com,kgw.com,khou.com,king5.com,kirotv.com,kitv.com,kmbc.com,knoxnews.com,kpho.com,kptv.com,kron4.com,ksdk.com,ksl.com,ktvb.com,kvue.com,kxan.com,latimes.com,lifehack.org,littlethings.com,mailtribune.com,mic.com,mirror.co.uk,msn.com,msnbc.com,msnbc.msn.com,myfoxboston.com,nbcnews.com,nbcnewyork.com,newburyportnews.com,news.bbc.co.uk,news.yahoo.com,news12.com,newschannel10.com,newsday.com,newser.com,newsmax.com,newyorker.com,nj.com,nj1015.com,npr.org,nydailynews.com,nypost.com,nytimes.com,palmbeachpost.com,patch.com,philly.com,phys.org,poconorecord.com,prnewswire.com,rare.us,realclearworld.com,record-eagle.com,richmond.com,rt.com,salemnews.com,salon.com,sfgate.com,slate.com,statesman.com,suntimes.com,takepart.com,telegraph.co.uk,theatlantic.com,thedailystar.com,theguardian.com,theroot.com,theverge.com,time.com,timesonline.co.uk,topix.com,usatoday.com,usatoday30.usatoday.com,usnews.com,vice.com,vox.com,wane.com,washingtonpost.com,washingtontimes.com,wave3.com,wavy.com,wbaltv.com,wbir.com,wcnc.com,wdbj7.com,westernjournalism.com,wfaa.com,wfsb.com,wftv.com,wgal.com,wishtv.com,wisn.com,wistv.com,wivb.com,wkyc.com,wlwt.com,wmur.com,woodtv.com,wpxi.com,wsbtv.com,wsfa.com,wsmv.com,wsoctv.com,wthr.com,wtnh.com,wtsp.com,wwltv.com,wyff4.com,wzzm13.com',
-    'news' ],
-  [ 'aei.org,breitbart.com,conservativetalknow.com,conservativetribune.com,dailykos.com,ddo.com,drudgereport.com,dscc.org,foreignpolicy.com,franklinprosperityreport.com,freedomworks.org,macleans.ca,mediamatters.org,militarytimes.com,nationaljournal.com,nationalreview.com,politicalwire.com,politico.com,pressrepublican.com,qpolitical.com,realclearpolitics.com,talkingpointsmemo.com,thehill.com,thenation.com,thinkprogress.org,tnr.com,worldoftanks.eu',
-    'news' ],
-  [ 'americanscientist.org,discovermagazine.com,iflscience.com,livescience.com,nasa.gov,nationalgeographic.com,nature.com,newscientist.com,popsci.com,sciencedaily.com,sciencemag.org,sciencenews.org,scientificamerican.com,space.com,zmescience.com',
-    'science' ],
-  [ 'accuweather.com,intellicast.com,noaa.gov,ssa.gov,theweathernetwork.com,weather.com,weather.gov,weather.yahoo.com,weatherbug.com,weatherunderground.com,weatherzone.com.au,wunderground.com,www.weather.com',
-    'weather' ],
-  [ 'bhphotovideo.com,bigfolio.com,bigstockphoto.com,cameralabs.com,canonrumors.com,canstockphoto.com,digitalcamerareview.com,dpreview.com,expertphotography.com,gettyimages.com,icp.org,imaging-resource.com,intothedarkroom.com,istockphoto.com,nikonusa.com,photos.com,shutterstock.com,slrgear.com,the-digital-picture.com,thephotoargus.com,usa.canon.com,whatdigitalcamera.com,zenfolio.com',
-    'photography' ],
-  [ 'abercrombie.com,ae.com,aeropostale.com,anthropologie.com,bananarepublic.com,buycostumes.com,chadwicks.com,express.com,forever21.com,freepeople.com,hm.com,hollisterco.com,jcrew.com,jessicalondon.com,kingsizemen.com,lordandtaylor.com,lulus.com,metrostyle.com,nomorerack.com,oldnavy.com,oldnavy.gap.com,polyvore.com,rackroomshoes.com,ralphlauren.com,refinery29.com,roamans.com,sammydress.com,shop.nordstrom.com,shopbop.com,topshop.com,urbanoutfitters.com,victoriassecret.com,wetseal.com,womanwithin.com',
-    'shopping' ],
-  [ 'bizrate.com,compare99.com,coupons.com,dealtime.com,epinions.com,junglee.com,kijiji.ca,pricegrabber.com,pronto.com,redplum.com,retailmenot.com,shopping.com,shopzilla.com,smarter.com,valpak.com',
-    'shopping' ],
-  [ '123greetings.com,1800baskets.com,1800flowers.com,americangreetings.com,birthdayexpress.com,bluemountain.com,e-cards.com,egreetings.com,florists.com,ftd.com,gifts.com,groupcard.com,harryanddavid.com,hipstercards.com,kabloom.com,personalcreations.com,proflowers.com,redenvelope.com,someecards.com',
-    'flowers & gifts' ],
-  [ '6pm.com,alibaba.com,aliexpress.com,amazon.co.uk,amazon.com,asos.com,bathandbodyworks.com,bloomingdales.com,bradsdeals.com,buy.com,cafepress.com,circuitcity.com,clarkhoward.com,consumeraffairs.com,costco.com,cvs.com,dhgate.com,diapers.com,dillards.com,ebates.com,ebay.com,ebaystores.com,etsy.com,fingerhut.com,groupon.com,hsn.com,jcpenney.com,kmart.com,kohls.com,kroger.com,lowes.com,macys.com,menards.com,nextag.com,nordstrom.com,orientaltrading.com,overstock.com,qvc.com,racked.com,rewardsnetwork.com,samsclub.com,sears.com,sephora.com,shopathome.com,shopify.com,shopstyle.com,slickdeals.net,soap.com,staples.com,target.com,toptenreviews.com,vistaprint.com,walgreens.com,walmart.ca,walmart.com,wayfair.com,zappos.com,zazzle.com,zulily.com',
-    'shopping' ],
-  [ 'acehardware.com,ashleyfurniture.com,bedbathandbeyond.com,brylanehome.com,casa.com,cb2.com,crateandbarrel.com,dwr.com,ethanallen.com,furniture.com,harborfreight.com,hayneedle.com,homedecorators.com,homedepot.com,ikea.com,info.ikea-usa.com,landofnod.com,pier1.com,plowhearth.com,potterybarn.com,restorationhardware.com,roomandboard.com,westelm.com,williams-sonoma.com',
-    'home shopping' ],
-  [ 'alexandermcqueen.com,bergdorfgoodman.com,bottegaveneta.com,burberry-bluelabel.com,burberry.com,chanel.com,christianlouboutin.com,coach.com,diesel.com,dior.com,dolcegabbana.com,dolcegabbana.it,fendi.com,ferragamo.com,giorgioarmani.com,givenchy.com,gucci.com,guess.com,hermes.com,jeanpaulgaultier.com,jimmychoo.com,juicycouture.com,katespade.com,louisvuitton.com,manoloblahnik.com,marcjacobs.com,neimanmarcus.com,net-a-porter.com,paulsmith.co.uk,prada.com,robertocavalli.com,saksfifthavenue.com,toryburch.com,valentino.com,versace.com,vuitton.com,ysl.com,yslbeautyus.com',
-    'luxury shopping' ],
-  [ 'bargainseatsonline.com,livenation.com,stubhub.com,ticketfly.com,ticketliquidator.com,ticketmaster.com,tickets.com,ticketsnow.com,ticketweb.com,vividseats.com',
-    'events & tickets' ],
-  [ 'babiesrus.com,brothers-brick.com,etoys.com,fao.com,fisher-price.com,hasbro.com,hasbrotoyshop.com,lego.com,legoland.com,mattel.com,toys.com,toysrus.com,toystogrowon.com,toywiz.com',
-    'toys & games' ],
-  [ 'challengegames.nfl.com,fantasy.nfl.com,fantasyfootballblog.net,fantasyfootballcafe.com,fantasyfootballnerd.com,fantasysmarts.com,fftoday.com,fftoolbox.com,football.fantasysports.yahoo.com,footballsfuture.com,mrfootball.com,officefootballpool.com,thehuddle.com',
-    'fantasy football' ],
-  [ 'dailyjoust.com,draftday.com,draftking.com,draftkings.com,draftstreet.com,fanduel.com,realmoneyfantasyleagues.com,thedailyaudible.com',
-    'fantasy sports' ],
-  [ 'cdmsports.com,fanball.com,fantasyguru.com,fantasynews.cbssports.com,fantasyquestions.com,fantasyrundown.com,fantasysharks.com,fantasysports.yahoo.com,fantazzle.com,fantrax.com,fleaflicker.com,junkyardjake.com,kffl.com,mockdraftcentral.com,myfantasyleague.com,rototimes.com,rotowire.com,rotoworld.com,rtsports.com,whatifsports.com',
-    'fantasy sports' ],
-  [ 'football.about.com,football.com,footballoutsiders.com,nationalfootballpost.com,nflalumni.org,nflpa.com,nfltraderumors.co,profootballhof.com,profootballtalk.com,profootballtalk.nbcsports.com,profootballweekly.com',
-    'football' ],
-  [ '49ers.com,atlantafalcons.com,azcardinals.com,baltimoreravens.com,bengals.com,buccaneers.com,buffalobills.com,chargers.com,chicagobears.com,clevelandbrowns.com,colts.com,dallascowboys.com,denverbroncos.com,detroitlions.com,giants.com,houstontexans.com,jaguars.com,kcchiefs.com,miamidolphins.com,neworleanssaints.com,newyorkjets.com,packers.com,panthers.com,patriots.com,philadelphiaeagles.com,raiders.com,redskins.com,seahawks.com,steelers.com,stlouisrams.com,titansonline.com,vikings.com',
-    'football' ],
-  [ 'baseball-reference.com,baseballamerica.com,europeantour.com,golf.com,golfdigest.com,lpga.com,milb.com,minorleagueball.com,mlb.com,mlb.mlb.com,nascar.com,nba.com,ncaa.com,nhl.com,pga.com,pgatour.com,prowrestling.com,surfermag.com,surfline.com,surfshot.com,thehockeynews.com,tsn.com,ufc.com,worldgolfchampionships.com,wwe.com',
-    'sports' ],
-  [ '247sports.com,active.com,armslist.com,basketball-reference.com,bigten.org,bleacherreport.com,bleedinggreennation.com,bloodyelbow.com,cagesideseats.com,cbssports.com,cinesport.com,collegespun.com,cricbuzz.com,crictime.com,csnphilly.com,csnwashington.com,cstv.com,eastbay.com,espn.com,espn.go.com,espncricinfo.com,espnfc.com,espnfc.us,espnradio.com,eteamz.com,fanatics.com,fansided.com,fbschedules.com,fieldandstream.com,flightclub.com,foxsports.com,givemesport.com,goduke.com,goheels.com,golfchannel.com,golfnow.com,grantland.com,grindtv.com,hoopshype.com,icc-cricket.com,imleagues.com,kentuckysportsradio.com,larrybrownsports.com,leaguelineup.com,maxpreps.com,mlbtraderumors.com,mmafighting.com,mmajunkie.com,mmamania.com,msn.foxsports.com,myscore.com,nbcsports.com,nbcsports.msnbc.com,nesn.com,rantsports.com,realclearsports.com,reserveamerica.com,rivals.com,runnersworld.com,sbnation.com,scout.com,sherdog.com,si.com,speedsociety.com,sportingnews.com,sports.yahoo.com,sportsillustrated.cnn.com,sportsmanias.com,sportsmonster.us,sportsonearth.com,stack.com,teamworkonline.com,thebiglead.com,thescore.com,trails.com,triblive.com,upickem.net,usatodayhss.com,watchcric.net,yardbarker.com',
-    'sports news' ],
-  [ 'adidas.com,backcountry.com,backcountrygear.com,cabelas.com,champssports.com,competitivecyclist.com,dickssportinggoods.com,finishline.com,footlocker.com,ladyfootlocker.com,modells.com,motosport.com,mountaingear.com,newbalance.com,nike.com,patagonia.com,puma.com,reebok.com,sportsmansguide.com,steepandcheap.com,tgw.com,thenorthface.com',
-    'sports & outdoor goods' ],
-  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
-    'technology' ],
-  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
-    'technology' ],
-  [ 'adobe.com,asus.com,avast.com,data.com,formstack.com,gboxapp.com,gotomeeting.com,hp.com,htc.com,ibm.com,intel.com,java.com,logme.in,mcafee.com,mcafeesecure.com,microsoftstore.com,norton.com,office.com,office365.com,opera.com,oracle.com,proboards.com,samsung.com,sourceforge.net,squarespace.com,techtarget.com,ultipro.com,uniblue.com,web.com,winzip.com',
-    'technology' ],
-  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,afterdawn.com,akihabaranews.com,appsrumors.com,avg.com,belkin.com,besttechinfo.com,betanews.com,botcrawl.com,breakingmuscle.com,cheap-phones.com,chip.de,chip.eu,citeworld.com,cleanpcremove.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,csoonline.com,daniweb.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dslreports.com,edugeek.net,eetimes.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,gizmodo.co.uk,globalsecurity.org,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,it168.com,itechpost.com,itpro.co.uk,jailbreaknation.com,laptoping.com,lightreading.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pchome.net,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,programming4.us,quickpwn.com,redmondpie.com,redorbit.com,safer-networking.org,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,sparkfun.com,speedguide.net,stuff.tv,symantec.com,taplikahome.com,techdailynews.net,techeblog.com,techie-buzz.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techradar.com,techshout.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,thewindowsclub.com,tomsitpro.com,trustedreviews.com,tuaw.com,tweaktown.com,unwiredview.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wp-themes.com,xml.com,zol.com.cn',
-    'technology' ],
-  [ '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',
-    'Mozilla' ],
-  [ 'addictivetips.com,allthingsd.com,anandtech.com,androidcentral.com,androidpolice.com,arstechnica.com,bgr.com,boygeniusreport.com,cio.com,cnet.com,computerworld.com,crn.com,electronista.com,engadget.com,extremetech.com,fastcocreate.com,fastcodesign.com,fastcoexist.com,frontlinek12.com,gigaom.com,gizmag.com,gizmodo.com,greenbot.com,howtogeek.com,idigitaltimes.com,imore.com,informationweek.com,infoworld.com,itworld.com,kioskea.net,laptopmag.com,leadpages.net,lifehacker.com,mashable.com,networkworld.com,news.cnet.com,nwc.com,pastebin.com,pcadvisor.co.uk,pcmag.com,pcworld.com,phonearena.com,reviewed.com,serverfault.com,siteadvisor.com,slashdot.org,techcrunch.com,techdirt.com,techhive.com,technewsworld.com,techrepublic.com,techweb.com,tomsguide.com,tomshardware.com,ubergizmo.com,venturebeat.com,wired.com,xda-developers.com,zdnet.com',
-    'technology news' ],
-  [ 'bestbuy.ca,bestbuy.com,cdw.com,compusa.com,computerlivehelp.co,cyberguys.com,dell.com,digitalinsight.com,directron.com,ebuyer.com,frontierpc.com,frys-electronics-ads.com,frys.com,geeks.com,gyazo.com,homestead.com,lenovo.com,macmall.com,microcenter.com,miniinthebox.com,mwave.com,newegg.com,officedepot.com,outletpc.com,outpost.com,radioshack.com,rakuten.com,tigerdirect.com',
-    'tech retail' ],
-  [ 'chat.com,fring.com,hello.firefox.com,oovoo.com,viber.com',
-    'video chat' ],
-  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,l10n.mozilla.org,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,speckyboy.com,stackoverflow.com,status.modern.ie,validator.w3.org,w3.org,webreference.com,whatcanidoformozilla.org',
-    'web development' ],
-  [ 'classroom.google.com,codeacademy.org,codecademy.com,codeschool.com,codeyear.com,elearning.ut.ac.id,how-to-build-websites.com,htmlcodetutorial.com,htmldog.com,htmlplayground.com,learn.jquery.com,quackit.com,roseindia.net,teamtreehouse.com,tizag.com,tutorialspoint.com,udacity.com,w3schools.com,webdevelopersnotes.com',
-    'webdev education' ],
-  [ 'att.com,att.net,attonlineoffers.com,bell.ca,bellsouth.com,cableone.net,cablevision.com,centurylink.com,centurylink.net,centurylinkquote.com,charter-business.com,charter.com,charter.net,chartercabledeals.com,chartermedia.com,comcast.com,comcast.net,cox.com,cox.net,coxnewsweb.com,directv.com,dish.com,dishnetwork.com,freeconferencecall.com,frontier.com,hughesnet.com,liveitwithcharter.com,mycenturylink.com,mydish.com,net10.com,officialtvstream.com.es,optimum.com,optimum.net,paygonline.com,paytm.com,qwest.com,rcn.com,rebtel.com,ringcentral.com,straighttalkbyop.com,swappa.com,textem.net,timewarner.com,timewarnercable.com,tracfone.com,verizon.com,verizon.net,voipo.com,vonagebusiness.com,wayport.net,whistleout.com,wildblue.net,windstream.net,windstreambusiness.net,wowway.com,ww2.cox.com,xfinity.com',
-    'telecommunication' ],
-  [ 'alltel.com,assurancewireless.com,attsavings.com,boostmobile.com,boostmobilestore.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,motorola.com,mycricket.com,myfamilymobile.com,nextel.com,nokia.com,nokiausa.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,sprintpcs.com,straighttalk.com,t-mobile.co.uk,t-mobile.com,tmobile.com,tracfonewireless.com,uscellular.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vodaphone.co.uk,vonange.com,vzwshop.com,wireless.att.com',
-    'mobile carrier' ],
-  [ 'aa.com,aerlingus.com,airasia.com,aircanada.com,airfrance.com,airindia.com,alaskaair.com,alaskaairlines.com,allegiantair.com,britishairways.com,cathaypacific.com,china-airlines.com,continental.com,delta.com,deltavacations.com,dragonair.com,easyjet.com,elal.co.il,emirates.com,flightaware.com,flyfrontier.com,frontierairlines.com,hawaiianair.com,iberia.com,jetairways.com,jetblue.com,klm.com,koreanair.com,kuwait-airways.com,lan.com,lufthansa.com,malaysiaairlines.com,mihinlanka.com,nwa.com,qantas.com.au,qatarairways.com,ryanair.com,singaporeair.com,smartfares.com,southwest.com,southwestvacations.com,spiritair.com,spiritairlines.com,thaiair.com,united.com,usairways.com,virgin-atlantic.com,virginamerica.com,virginblue.com.au',
-    'travel & airline' ],
-  [ 'carnival.com,celebrity-cruises.com,celebritycruises.com,costacruise.com,cruise.com,cruiseamerica.com,cruisecritic.com,cruisedirect.com,cruisemates.com,cruises.com,cruisesonly.com,crystalcruises.com,cunard.com,disneycruise.disney.go.com,hollandamerica.com,ncl.com,pocruises.com,princess.com,royalcaribbean.com,royalcaribbean.cruiselines.com,rssc.com,seabourn.com,silversea.com,starcruises.com,vikingrivercruises.com,windstarcruises.com',
-    'travel & cruise' ],
-  [ 'agoda.com,airbnb.com,beaches.com,bedandbreakfast.com,bestwestern.com,booking.com,caesars.com,choicehotels.com,comfortinn.com,daysinn.com,dealbase.com,doubletree3.hilton.com,embassysuites.com,fairmont.com,flipkey.com,fourseasons.com,greatwolf.com,hamptoninn.hilton.com,hamptoninn3.hilton.com,hhonors3.hilton.com,hilton.com,hiltongardeninn3.hilton.com,hiltonworldwide.com,holidayinn.com,homeaway.com,hotelclub.com,hotelopia.com,hotels.com,hotelscombined.com,hyatt.com,ihg.com,laterooms.com,lhw.com,lq.com,mandarinoriental.com,marriott.com,motel6.com,omnihotels.com,radisson.com,ramada.com,rci.com,reservationcounter.com,resortvacationstogo.com,ritzcarlton.com,roomkey.com,sheraton.com,starwoodhotels.com,starwoodhotelshawaii.com,super8.com,thetrain.com,vacationhomerentals.com,vacationrentals.com,vrbo.com,wyndhamrewards.com',
-    'hotel & resort' ],
-  [ 'airfarewatchdog.com,airliners.net,atlanta-airport.com,budgettravel.com,cntraveler.com,cntraveller.com,destination360.com,flightstats.com,flyertalk.com,fodors.com,frommers.com,letsgo.com,lonelyplanet.com,matadornetwork.com,perfectvacation.co,ricksteves.com,roughguides.com,timeout.com,travelalberta.us,travelandleisure.com,travelchannel.com,traveler.nationalgeographic.com,travelmath.com,traveltune.com,tripadvisor.com,vegas.com,viator.com,virtualtourist.com,wikitravel.org,worldtravelguide.net',
-    'travel' ],
-  [ 'aavacations.com,applevacations.com,avianca.com,bookingbuddy.com,bookit.com,cheapair.com,cheapcaribbean.com,cheapflights.com,cheapoair.com,cheaptickets.com,chinahighlights.com,costcotravel.com,ctrip.com,despegar.com,edreams.net,expedia.ca,expedia.com,fareboom.com,farebuzz.com,farecast.live.com,farecompare.com,faregeek.com,flightnetwork.com,funjet.com,golastminute.com,hipmunk.com,hotwire.com,ifly.com,justairticket.com,kayak.com,lastminute.com,lastminutetravel.com,lowestfare.com,lowfares.com,momondo.com,onetime.com,onetravel.com,orbitz.com,otel.com,priceline.com,pricelinevisa.com,sidestep.com,skyscanner.com,smartertravel.com,statravel.com,tigerair.com,travelocity.com,travelonbids.com,travelzoo.com,tripsta.com,trivago.com,universalorlando.com,universalstudioshollywood.com,vacationexpress.com,venere.com,webjet.com,yatra.com',
-    'travel' ],
-  [ 'airportrentalcars.com,alamo.com,amtrak.com,anytransitguide.com,avis.com,boltbus.com,budget.com,carrentalexpress.com,carrentals.com,coachusa.com,dollar.com,e-zrentacar.com,enterprise.com,europcar.com,foxrentacar.com,gotobus.com,greyhound.com,hertz.com,hertzondemand.com,indianrail.gov.in,irctc.co.in,megabus.com,mta.info,nationalcar.com,nationalrail.co.uk,njtransit.com,paylesscar.com,paylesscarrental.com,peterpanbus.com,raileurope.com,rentalcars.com,rideuta.com,stagecoachbus.com,thrifty.com,uber.com,wanderu.com,zipcar.com',
-    'travel & transit' ],
-  [ 'bulbagarden.net,cheatcc.com,cheatmasters.com,cheats.ign.com,comicvine.com,computerandvideogames.com,counter-strike.net,escapistmagazine.com,gamedaily.com,gamefront.com,gameinformer.com,gamerankings.com,gamespot.com,gamesradar.com,gamestop.com,gametrailers.com,gamezone.com,giantbomb.com,ign.com,kotaku.com,metacritic.com,minecraft-server-list.com,minecraftforge.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mmo-champion.com,mojang.com,pcgamer.com,planetminecraft.com,supercheats.com,thesims.com,totaljerkface.com,unity3d.com,vg247.com,wowhead.com',
-    'gaming' ],
-  [ 'a10.com,absolutist.com,addictinggames.com,aeriagames.com,agame.com,alpha-wars.com,arcadeyum.com,armorgames.com,ballerarcade.com,battle.net,battlefield.com,bigfishgames.com,bioware.com,bitrhymes.com,candystand.com,conjurorthegame.com,crazymonkeygames.com,crusharcade.com,curse.com,cuttherope.net,dreammining.com,dressupgames.com,ea.com,easports.com,fps-pb.com,freearcade.com,freeonlinegames.com,friv.com,funplusgame.com,gamefly.com,gameforge.com,gamehouse.com,gamejolt.com,gameloft.com,gameoapp.com,gamepedia.com,gamersfirst.com,games.com,games.yahoo.com,gamesgames.com,gamezhero.com,gamingwonderland.com,ganymede.eu,goodgamestudios.com,gpotato.com,gsn.com,guildwars2.com,hirezstudios.com,igg.com,iwin.com,kahoot.it,king.com,kizi.com,kongregate.com,leagueoflegends.com,lolking.net,maxgames.com,minecraft-mp.com,minecraft.net,miniclip.com,mmo-play.com,mmorpg.com,mobafire.com,moviestarplanet.com,myonlinearcade.com,needforspeed.com,newgrounds.com,nexusmods.com,nintendo.com,noxxic.com,onrpg.com,origin.com,pch.com,peakgames.net,playstation.com,pogo.com,pokemon.com,popcap.com,primarygames.com,r2games.com,railnation.us,riotgames.com,roblox.com,rockstargames.com,runescape.com,shockwave.com,silvergames.com,spore.com,steamcommunity.com,steampowered.com,stickpage.com,swtor.com,tetrisfriends.com,thegamerstop.com,thesims3.com,twitch.tv,warthunder.com,wildtangent.com,worldoftanks.com,worldofwarcraft.com,worldofwarplanes.com,worldofwarships.com,xbox.com,y8.com,zone.msn.com,zynga.com,zyngawithfriends.com',
-    'online gaming' ],
-]);
-
 // Only allow link urls that are http(s)
 const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
 
 // Only allow link image urls that are https or data
 const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
 
 // Only allow urls to Mozilla's CDN or empty (for data URIs)
 const ALLOWED_URL_BASE = new Set(["mozilla.net", ""]);
@@ -764,24 +592,16 @@ let DirectoryLinksProvider = {
    */
   getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
     // Use the provided link if it's already enhanced
     return link.enhancedImageURI && link ? link :
            this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
   },
 
   /**
-   * Get the display name of an allowed frecent sites. Returns undefined for a
-   * unallowed frecent sites.
-   */
-  getFrecentSitesName(sites) {
-    return ALLOWED_FRECENT_SITES.get(sites.join(","));
-  },
-
-  /**
    * Check if a url's scheme is in a Set of allowed schemes and if the base
    * domain is allowed.
    * @param url to check
    * @param allowed Set of allowed schemes
    * @param checkBase boolean to check the base domain
    */
   isURLAllowed(url, allowed, checkBase) {
     // Assume no url is an allowed url
@@ -832,28 +652,27 @@ let DirectoryLinksProvider = {
       let validityFilter = function(link) {
         // Make sure the link url is allowed and images too if they exist
         return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES, false) &&
                this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES, checkBase) &&
                this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES, checkBase);
       }.bind(this);
 
       rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
-        // Only allow suggested links with approved frecent sites
-        let name = this.getFrecentSitesName(link.frecent_sites);
-        if (name == undefined) {
+        // Suggested sites must have an adgroup name.
+        if (!link.adgroup_name) {
           return;
         }
 
         let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
           ParserUtils.SanitizerDropForms |
           ParserUtils.SanitizerDropNonCSSPresentation;
 
         link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
-        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0) || name);
+        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0));
         link.lastVisitDate = rawLinks.suggested.length - position;
         // check if link wants to avoid inadjacent sites
         if (link.check_inadjacency) {
           this._avoidInadjacentSites = true;
         }
 
         // We cache suggested tiles here but do not push any of them in the links list yet.
         // The decision for which suggested tile to include will be made separately.
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -62,49 +62,53 @@ const BinaryInputStream = CC("@mozilla.o
                               "setInputStream");
 
 let gLastRequestPath;
 
 let suggestedTile1 = {
   url: "http://turbotax.com",
   type: "affiliate",
   lastVisitDate: 4,
+  adgroup_name: "Adgroup1",
   frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile2 = {
   url: "http://irs.gov",
   type: "affiliate",
   lastVisitDate: 3,
+  adgroup_name: "Adgroup2",
   frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "freetaxusa.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile3 = {
   url: "http://hrblock.com",
   type: "affiliate",
   lastVisitDate: 2,
+  adgroup_name: "Adgroup3",
   frecent_sites: [
     "taxact.com",
     "freetaxusa.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile4 = {
   url: "http://sponsoredtile.com",
   type: "sponsored",
   lastVisitDate: 1,
+  adgroup_name: "Adgroup4",
   frecent_sites: [
     "sponsoredtarget.com"
   ]
 }
 let suggestedTile5 = {
   url: "http://eviltile.com",
   type: "affiliate",
   lastVisitDate: 5,
@@ -291,19 +295,16 @@ add_task(function test_updateSuggestedTi
 
   // 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);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -408,26 +409,22 @@ add_task(function test_updateSuggestedTi
   topSites = [];
   testObserver = new TestRemovingSuggestedTile();
   DirectoryLinksProvider.addObserver(testObserver);
   DirectoryLinksProvider.onManyLinksChanged();
   yield testObserver.promise;
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_suggestedLinksMap() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "testing map";
-
   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);
@@ -447,30 +444,26 @@ add_task(function test_suggestedLinksMap
   let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
   do_check_eq(suggestedSites.indexOf("sponsoredtarget.com"), 5);
   do_check_eq(suggestedSites.length, Object.keys(expected_data).length);
 
   DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
     let suggestedLinksItr = suggestedLinks.values();
     for (let link of expected_data[site]) {
       let linkCopy = JSON.parse(JSON.stringify(link));
-      linkCopy.targetedName = "testing map";
+      linkCopy.targetedName = link.adgroup_name;
       linkCopy.explanation = "";
       isIdentical(suggestedLinksItr.next().value, linkCopy);
     }
   })
 
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
 });
 
 add_task(function test_topSitesWithSuggestedLinks() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   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;
@@ -508,40 +501,41 @@ add_task(function test_topSitesWithSugge
 
   // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
   topSites.push(popped);
   expectedTopSitesWithSuggestedLinks.push(popped);
   DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
   isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
 });
 
 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/";
   let title = "the title";
   let type = "affiliate";
   let url = "http://test.url/";
+  let adgroup_name = "Mozilla";
   let data = {
     suggested: [{
       frecent_sites,
       imageURI,
       title,
       type,
-      url
+      url,
+      adgroup_name
     }]
   };
   let dataURI = "data:application/json," + escape(JSON.stringify(data));
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   // Wait for links to get loaded
   let gLinks = NewTabUtils.links;
@@ -570,32 +564,31 @@ add_task(function test_suggestedAttribut
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
 });
 
 add_task(function test_frequencyCappedSites_views() {
   Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   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 = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
       url: testUrl,
-      frequency_caps: {daily: 5}
+      frequency_caps: {daily: 5},
+      adgroup_name: "Test"
     }],
     directory: [{
       type: "organic",
       url: "http://directory.site/"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
 
@@ -639,41 +632,39 @@ add_task(function test_frequencyCappedSi
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_frequencyCappedSites_click() {
   Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   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 = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
-      url: testUrl
+      url: testUrl,
+      adgroup_name: "Test"
     }],
     directory: [{
       type: "organic",
       url: "http://directory.site/"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
 
@@ -711,17 +702,16 @@ add_task(function test_frequencyCappedSi
   // Make sure the link disappears after the first click
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("click");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_reportSitesAction() {
@@ -1212,26 +1202,24 @@ add_task(function test_DirectoryLinksPro
   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() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   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", frecent_sites: ["test.com"]}
+      {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", adgroup_name: "Test", frecent_sites: ["test.com"]}
     ],
     "directory": [
       {url: "http://example.net", enhancedImageURI: "data:,net2", title:"DirectoryTitle"}
     ]
   };
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
@@ -1257,17 +1245,16 @@ add_task(function test_DirectoryLinksPro
 
   // Check that the suggested tile with the same URL replaces the directory tile.
   links = gLinks.getLinks();
   do_check_eq(links.length, 1);
   do_check_eq(links[0].title, "SuggestedTitle");
   do_check_eq(links[0].enhancedImageURI, "data:,net1");
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
 });
 
 add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
   function checkDefault(expected) {
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
@@ -1307,19 +1294,16 @@ add_task(function test_timeSensetiveSugg
   // Initial setup
   let topSites = ["site0.com", "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);
 
   let testObserver = new TestTimingRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -1419,17 +1403,16 @@ add_task(function test_timeSensetiveSugg
     do_check_true(DirectoryLinksProvider._campaignTimeoutID);
     DirectoryLinksProvider._clearCampaignTimeout();
     deferred.resolve();
   });
   yield deferred.promise;
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_setupStartEndTime() {
   let currentTime = Date.now();
   let dt = new Date(currentTime);
@@ -1723,20 +1706,16 @@ add_task(function test_DirectoryLinksPro
 
 add_task(function test_DirectoryLinksProvider_anonymous() {
   do_check_true(DirectoryLinksProvider._newXHR().mozAnon);
 });
 
 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 origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "testing map";
-
   let data = {"suggested": [suggestedTile5]};
   let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data));
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
   do_check_eq(suggestedSites.indexOf("eviltarget.com"), 0);
@@ -1755,19 +1734,16 @@ add_task(function test_inadjecentSites()
   // 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);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -1913,38 +1889,36 @@ add_task(function test_inadjecentSites()
   do_check_false(DirectoryLinksProvider._isInadjacentLink({}));
 
   // test _checkForInadjacentSites
   do_check_true(DirectoryLinksProvider._checkForInadjacentSites());
 
   // Cleanup
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider._inadjacentSitesUrl = origInadjacentSitesUrl;
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_reportPastImpressions() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   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 = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
-      url: testUrl
+      url: testUrl,
+      adgroup_name: "Test"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   // make DirectoryLinksProvider load json
   let loadPromise = Promise.defer();
   DirectoryLinksProvider.getLinks(_ => {loadPromise.resolve();});
@@ -2021,12 +1995,11 @@ add_task(function test_reportPastImpress
   yield sendPingAndTest("click", "unpin", 2);
   sites.isPinned = _ => false;
   yield sendPingAndTest("click", "click", 2);
   sites.isPinned = _ => false;
   yield sendPingAndTest("click", "block", 2);
 
   // Cleanup.
   done = true;
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
--- a/browser/themes/shared/devtools/performance.css
+++ b/browser/themes/shared/devtools/performance.css
@@ -584,16 +584,24 @@ menuitem.marker-color-graphs-grey:before
 
 #jit-optimizations-view {
   width: 350px;
   overflow-x: hidden;
   overflow-y: auto;
   min-width: 200px;
 }
 
+#optimizations-graph {
+  height: 30px;
+}
+
+#jit-optimizations-view.empty #optimizations-graph {
+  display: none !important;
+}
+
 /* override default styles for tree widget */
 #jit-optimizations-view .tree-widget-empty-text {
   font-size: inherit;
   padding: 0px;
   margin: 8px;
 }
 
 #jit-optimizations-view:not(.empty) .tree-widget-empty-text {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -47,23 +47,23 @@
   --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
 
   --panel-separator-color: ThreeDLightShadow;
 
   --urlbar-separator-color: hsla(0,0%,16%,.2);
 }
 
 #nav-bar[brighttext] {
-  --toolbarbutton-hover-background: rgba(255,255,255,.1);
-  --toolbarbutton-hover-bordercolor: rgba(255,255,255,.1);
+  --toolbarbutton-hover-background: rgba(255,255,255,.15);
+  --toolbarbutton-hover-bordercolor: rgba(255,255,255,.15);
   --toolbarbutton-hover-boxshadow: none;
 
-  --toolbarbutton-active-background: rgba(255,255,255,.15);
-  --toolbarbutton-active-bordercolor: rgba(255,255,255,.15);
-  --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.15) inset;
+  --toolbarbutton-active-background: rgba(255,255,255,.3);
+  --toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
+  --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -837,18 +837,18 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 #nav-bar #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
   /* horizontal padding + border + actual icon width */
   width: 31px;
 }
 
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
-  padding-top: 8px;
-  padding-bottom: 8px;
+  padding-top: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
+  padding-bottom: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
 }
 
 #nav-bar .toolbaritem-combined-buttons {
   margin-left: 2px;
   margin-right: 2px;
 }
 
 #nav-bar .toolbaritem-combined-buttons > .toolbarbutton-1 {
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -18,24 +18,24 @@ namespace devtools {
 class HeapSnapshot;
 } // namespace devtools
 
 namespace dom {
 
 class ThreadSafeChromeUtils
 {
 public:
-  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
   static void SaveHeapSnapshot(GlobalObject& global,
                                JSContext* cx,
-                               const nsAString& filePath,
                                const HeapSnapshotBoundaries& boundaries,
+                               nsAString& filePath,
                                ErrorResult& rv);
 
-  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
   static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(GlobalObject& global,
                                                                    JSContext* cx,
                                                                    const nsAString& filePath,
                                                                    ErrorResult& rv);
 };
 
 class ChromeUtils : public ThreadSafeChromeUtils
 {
--- a/dom/webidl/ThreadSafeChromeUtils.webidl
+++ b/dom/webidl/ThreadSafeChromeUtils.webidl
@@ -9,23 +9,25 @@
  * to Chrome. This interface is exposed in workers, while ChromeUtils is not.
  */
 [ChromeOnly, Exposed=(Window,System,Worker)]
 interface ThreadSafeChromeUtils {
   /**
    * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
    * restricted by |boundaries|, and write it to the provided file path.
    *
-   * @param filePath          The file path to write the heap snapshot to.
+   * @param boundaries        The portion of the heap graph to write.
    *
-   * @param boundaries        The portion of the heap graph to write.
+   * @returns                 The path to the file the heap snapshot was written
+   *                          to. This is guaranteed to be within the temp
+   *                          directory and its file name will match the regexp
+   *                          `\d+(\-\d+)?\.fxsnapshot`.
    */
   [Throws]
-  static void saveHeapSnapshot(DOMString filePath,
-                               optional HeapSnapshotBoundaries boundaries);
+  static DOMString saveHeapSnapshot(optional HeapSnapshotBoundaries boundaries);
 
   /**
    * Deserialize a core dump into a HeapSnapshot.
    *
    * @param filePath          The file path to read the heap snapshot from.
    */
   [Throws, NewObject]
   static HeapSnapshot readHeapSnapshot(DOMString filePath);
--- a/mobile/android/.eslintignore
+++ b/mobile/android/.eslintignore
@@ -16,9 +16,8 @@ locales/
 modules/ContactService.jsm
 
 # es7 proposed: array comprehensions
 #   https://github.com/eslint/espree/issues/125
 modules/WebappManager.jsm
 
 # Non-standard `(catch ex if ...)`
 components/Snippets.js
-modules/MatchstickApp.jsm
deleted file mode 100644
index ec0857ffd21e24211d02e16eb5ef347d1d56c3b8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index b944b3351d42ee5c29c56964325b152f2dcc8969..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/base/resources/values-v11/styles.xml
+++ b/mobile/android/base/resources/values-v11/styles.xml
@@ -93,19 +93,22 @@
     <style name="GeckoActionBar.Button" parent="android:style/Widget.Holo.Light.ActionButton">
         <item name="android:padding">8dip</item>
         <!-- The default implementation doesn't do any image scaling. Our custom menus mean we can't just use the same image
              in both menus and the actionbar without doing some scaling though. -->
         <item name="android:scaleType">centerInside</item>
     </style>
 
     <style name="GeckoActionBar.Button.MenuButton" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
-        <item name="android:scaleType">center</item>
+        <item name="android:scaleType">centerInside</item>
         <item name="android:background">@android:color/transparent</item>
-        <item name="android:src">@drawable/menu_light</item>
+        <item name="android:src">@drawable/menu</item>
+        <item name="android:tint">@color/toolbar_icon_grey</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:layout_marginBottom">16dp</item>
     </style>
 
     <style name="TabInput"></style>
 
     <style name="TabInput.TabWidget" parent="android:style/Widget.Holo.Light.TabWidget"/>
 
     <style name="TabInput.Tab" parent="android:style/Widget.Holo.Light.Tab">
         <item name="android:minHeight">@dimen/menu_item_row_height</item>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -725,17 +725,18 @@
     </style>
 
     <style name="GeckoActionBar.Button" parent="Widget.MenuItemActionBar">
         <item name="android:padding">8dp</item>
     </style>
 
     <style name="GeckoActionBar.Button.MenuButton">
         <item name="android:scaleType">center</item>
-        <item name="android:src">@drawable/menu_light</item>
+        <item name="android:src">@drawable/menu</item>
+        <item name="android:tint">@color/toolbar_icon_grey</item>
         <item name="android:contentDescription">@string/actionbar_menu</item>
         <item name="android:background">@android:color/transparent</item>
     </style>
 
     <style name="GeckoActionBar.Buttons">
         <item name="android:background">@android:color/transparent</item>
         <item name="android:textColor">@color/placeholder_active_grey</item>
         <item name="android:gravity">right</item>
--- a/mobile/android/chrome/content/.eslintrc
+++ b/mobile/android/chrome/content/.eslintrc
@@ -5,17 +5,16 @@ globals:
     Ci: false
     Cu: false
     NativeWindow: false
     PageActions: false
     ReaderMode: false
     SimpleServiceDiscovery: false
     TabMirror: false
     MediaPlayerApp: false
-    MatchstickApp: false
     RokuApp: false
     SearchEngines: false
     ConsoleAPI: true
     Point: false
     Rect: false
 
 rules:
     # Disabled stuff
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -15,30 +15,16 @@ var rokuDevice = {
   factory: function(aService) {
     Cu.import("resource://gre/modules/RokuApp.jsm");
     return new RokuApp(aService);
   },
   types: ["video/mp4"],
   extensions: ["mp4"]
 };
 
-var matchstickDevice = {
-  id: "matchstick:dial",
-  target: "urn:dial-multiscreen-org:service:dial:1",
-  filters: {
-    manufacturer: "openflint"
-  },
-  factory: function(aService) {
-    Cu.import("resource://gre/modules/MatchstickApp.jsm");
-    return new MatchstickApp(aService);
-  },
-  types: ["video/mp4", "video/webm"],
-  extensions: ["mp4", "webm"]
-};
-
 var mediaPlayerDevice = {
   id: "media:router",
   target: "media:router",
   factory: function(aService) {
     Cu.import("resource://gre/modules/MediaPlayerApp.jsm");
     return new MediaPlayerApp(aService);
   },
   types: ["video/mp4", "video/webm", "application/x-mpegurl"],
@@ -80,17 +66,16 @@ var CastingApps = {
 
   init: function ca_init() {
     if (!this.isCastingEnabled()) {
       return;
     }
 
     // Register targets
     SimpleServiceDiscovery.registerDevice(rokuDevice);
-    SimpleServiceDiscovery.registerDevice(matchstickDevice);
 
     // MediaPlayerDevice will notify us any time the native device list changes.
     mediaPlayerDevice.init();
     SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
 
     // Search for devices continuously every 120 seconds
     SimpleServiceDiscovery.search(120 * 1000);
 
deleted file mode 100644
--- a/mobile/android/modules/MatchstickApp.jsm
+++ /dev/null
@@ -1,375 +0,0 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["MatchstickApp"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-function log(msg) {
-  Services.console.logStringMessage(msg);
-}
-
-const MATCHSTICK_PLAYER_URL = "http://openflint.github.io/flint-player/player.html";
-
-const STATUS_RETRY_COUNT = 5;   // Number of times we retry a partial status
-const STATUS_RETRY_WAIT = 1000; // Delay between attempts in milliseconds
-
-/* MatchstickApp is a wrapper for interacting with a DIAL server.
- * The basic interactions all use a REST API.
- * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs
- */
-function MatchstickApp(aServer) {
-  this.server = aServer;
-  this.app = "~flintplayer";
-  this.resourceURL = this.server.appsURL + this.app;
-  this.token = null;
-  this.statusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  this.statusRetry = 0;
-}
-
-MatchstickApp.prototype = {
-  status: function status(aCallback) {
-    // Query the server to see if an application is already running
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("GET", this.resourceURL, true);
-    xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.token);
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200) {
-        let doc = xhr.responseXML;
-        let state = doc.querySelector("state").textContent;
-
-        // The serviceURL can be missing if the player is not completely loaded
-        let serviceURL = null;
-        let serviceNode = doc.querySelector("channelBaseUrl");
-        if (serviceNode) {
-          serviceURL = serviceNode.textContent + "/senders/" + this.token;
-        }
-
-        if (aCallback)
-          aCallback({ state: state, serviceURL: serviceURL });
-      } else {
-        if (aCallback)
-          aCallback({ state: "error" });
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback({ state: "error" });
-    }).bind(this), false);
-
-    xhr.send(null);
-  },
-
-  start: function start(aCallback) {
-    // Start a given app with any extra query data. Each app uses it's own data scheme.
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("POST", this.resourceURL, true);
-    xhr.overrideMimeType("text/xml");
-    xhr.setRequestHeader("Content-Type", "application/json");
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200 || xhr.status == 201) {
-        this.statusRetry = 0;
-
-        let response = JSON.parse(xhr.responseText);
-        this.token = response.token;
-        this.pingInterval = response.interval;
-
-        if (aCallback)
-          aCallback(true);
-      } else {
-        if (aCallback)
-          aCallback(false);
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback(false);
-    }).bind(this), false);
-
-    let data = {
-      type: "launch",
-      app_info: {
-        url: MATCHSTICK_PLAYER_URL,
-        useIpc: true,
-        maxInactive: -1
-      }
-    };
-
-    xhr.send(JSON.stringify(data));
-  },
-
-  stop: function stop(aCallback) {
-    // Send command to kill an app, if it's already running.
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("DELETE", this.resourceURL + "/run", true);
-    xhr.overrideMimeType("text/plain");
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.token);
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200) {
-        if (aCallback)
-          aCallback(true);
-      } else {
-        if (aCallback)
-          aCallback(false);
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback(false);
-    }).bind(this), false);
-
-    xhr.send(null);
-  },
-
-  remoteMedia: function remoteMedia(aCallback, aListener) {
-    this.status((aStatus) => {
-      if (aStatus.serviceURL) {
-        if (aCallback) {
-          aCallback(new RemoteMedia(aStatus.serviceURL, aListener, this));
-        }
-        return;
-      }
-
-      // It can take a few moments for the player app to load. Let's use a small delay
-      // and retry a few times.
-      if (this.statusRetry < STATUS_RETRY_COUNT) {
-        this.statusRetry++;
-        this.statusTimer.initWithCallback(() => {
-          this.remoteMedia(aCallback, aListener);
-        }, STATUS_RETRY_WAIT, Ci.nsITimer.TYPE_ONE_SHOT);
-      } else {
-        // Fail
-        if (aCallback) {
-          aCallback();
-        }
-      }
-    });
-  }
-}
-
-/* RemoteMedia provides a wrapper for using WebSockets and Flint protocol to control
- * the Matchstick media player
- * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs
- * See: https://github.com/openflint/flint-receiver-sdk/blob/gh-pages/v1/libs/mediaplayer.js
- */
-function RemoteMedia(aURL, aListener, aApp) {
-  this._active = false;
-  this._status = "uninitialized";
-
-  this.app = aApp;
-  this.listener = aListener;
-
-  this.pingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
-  let uri = Services.io.newURI(aURL, null, null);
-  this.ws = Cc["@mozilla.org/network/protocol;1?name=ws"].createInstance(Ci.nsIWebSocketChannel);
-  this.ws.initLoadInfo(null, // aLoadingNode
-                       Services.scriptSecurityManager.getSystemPrincipal(),
-                       null, // aTriggeringPrincipal
-                       Ci.nsILoadInfo.SEC_NORMAL,
-                       Ci.nsIContentPolicy.TYPE_WEBSOCKET);
-
-  this.ws.asyncOpen(uri, aURL, this, null);
-}
-
-// Used to give us a small gap between not pinging too often and pinging too late
-const PING_INTERVAL_BACKOFF = 200;
-
-RemoteMedia.prototype = {
-  _ping: function _ping() {
-    if (this.app.pingInterval == -1) {
-      return;
-    }
-
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("GET", this.app.resourceURL, true);
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.app.token);
-
-    xhr.addEventListener("load", () => {
-      if (xhr.status == 200) {
-        this.pingTimer.initWithCallback(() => {
-          this._ping();
-        }, this.app.pingInterval - PING_INTERVAL_BACKOFF, Ci.nsITimer.TYPE_ONE_SHOT);
-      }
-    });
-
-    xhr.send(null);
-  },
-
-  _changeStatus: function _changeStatus(status) {
-    if (this._status != status) {
-      this._status = status;
-      if ("onRemoteMediaStatus" in this.listener) {
-        this.listener.onRemoteMediaStatus(this);
-      }
-    }
-  },
-
-  _teardown: function _teardown() {
-    if (!this._active) {
-      return;
-    }
-
-    // Stop any queued ping event
-    this.pingTimer.cancel();
-
-    // Let the listener know we are finished
-    this._active = false;
-    if (this.listener && "onRemoteMediaStop" in this.listener) {
-      this.listener.onRemoteMediaStop(this);
-    }
-  },
-
-  _sendMsg: function _sendMsg(params) {
-    // Convert payload to a string
-    params.payload = JSON.stringify(params.payload);
-
-    try {
-      this.ws.sendMsg(JSON.stringify(params));
-    } catch (e if e.result == Cr.NS_ERROR_NOT_CONNECTED) {
-      // This shouldn't happen unless something gets out of sync with the
-      // connection. Let's make sure we try to cleanup.
-      this._teardown();
-    } catch (e) {
-      log("Send Error: " + e)
-    }
-  },
-
-  get active() {
-    return this._active;
-  },
-
-  get status() {
-    return this._status;
-  },
-
-  shutdown: function shutdown() {
-    this.ws.close(Ci.nsIWebSocketChannel.CLOSE_NORMAL, "shutdown");
-  },
-
-  play: function play() {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "PLAY",
-        requestId: "requestId-5",
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  pause: function pause() {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "PAUSE",
-        requestId: "requestId-4",
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  load: function load(aData) {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "LOAD",
-        requestId: "requestId-2",
-        media: {
-          contentId: aData.source,
-          contentType: "video/mp4",
-          metadata: {
-            title: "",
-            subtitle: ""
-          }
-        }
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  onStart: function(aContext) {
-    this._active = true;
-    if (this.listener && "onRemoteMediaStart" in this.listener) {
-      this.listener.onRemoteMediaStart(this);
-    }
-
-    this._ping();
-  },
-
-  onStop: function(aContext, aStatusCode) {
-    // This will be called for internal socket failures and timeouts. Make
-    // sure we cleanup.
-    this._teardown();
-  },
-
-  onAcknowledge: function(aContext, aSize) {},
-  onBinaryMessageAvailable: function(aContext, aMessage) {},
-
-  onMessageAvailable: function(aContext, aMessage) {
-    let msg = JSON.parse(aMessage);
-    if (!msg) {
-      return;
-    }
-
-    let payload = JSON.parse(msg.payload);
-    if (!payload) {
-      return;
-    }
-
-    // Handle state changes using the player notifications
-    if (payload.type == "MEDIA_STATUS") {
-      let status = payload.status[0];
-      let state = status.playerState.toLowerCase();
-      if (state == "playing") {
-        this._changeStatus("started");
-      } else if (state == "paused") {
-        this._changeStatus("paused");
-      } else if (state == "idle" && "idleReason" in status) {
-        // Make sure we are really finished. IDLE can be sent at other times.
-        let reason = status.idleReason.toLowerCase();
-        if (reason == "finished") {
-          this._changeStatus("completed");
-        }
-      }
-    }
-  },
-
-  onServerClose: function(aContext, aStatusCode, aReason) {
-    // This will be fired from _teardown when we close the websocket, but it
-    // can also be called for other internal socket failures and timeouts. We
-    // make sure the _teardown bails on reentry.
-    this._teardown();
-  }
-}
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -12,17 +12,16 @@ EXTRA_JS_MODULES += [
     'DelayedInit.jsm',
     'DownloadNotifications.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
     'JavaAddonManager.jsm',
     'JNI.jsm',
     'LightweightThemeConsumer.jsm',
-    'MatchstickApp.jsm',
     'MediaPlayerApp.jsm',
     'Messaging.jsm',
     'MulticastDNS.jsm',
     'NetErrorHelper.jsm',
     'Notifications.jsm',
     'OrderedBroadcast.jsm',
     'PageActions.jsm',
     'Prompt.jsm',
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -73,17 +73,16 @@ class MochitestArguments(ArgumentContain
                   "browser with --keep-open=false",
           }],
         [["--appname"],
          {"dest": "app",
           "default": None,
           "help": "Override the default binary used to run tests with the path provided, e.g "
                   "/usr/bin/firefox. If you have run ./mach package beforehand, you can "
                   "specify 'dist' to run tests against the distribution bundle's binary.",
-          "suppress": build_obj is not None,
           }],
         [["--utility-path"],
          {"dest": "utilityPath",
           "default": build_obj.bindir if build_obj is not None else None,
           "help": "absolute path to directory containing utility programs "
                   "(xpcshell, ssltunnel, certutil)",
           "suppress": True,
           }],
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -34,16 +34,21 @@
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
   const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
   const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
+  // Use backslashes instead of forward slashes due to memory reporting's hacky
+  // handling of URLs.
+  const XUL_NS =
+    "http:\\\\www.mozilla.org\\keymaster\\gatekeeper\\there.is.only.xul";
+
   let vsizeAmounts = [];
   let residentAmounts = [];
   let heapAllocatedAmounts = [];
   let storageSqliteAmounts = [];
 
   let jsGcHeapUsedGcThingsTotal = 0;
   let jsGcHeapUsedGcThings = {};
 
@@ -114,23 +119,26 @@
     if (aPath.includes('<anonymized')) {
         present.anonymizedWhenUnnecessary = aPath;
     }
   }
 
   function handleReportAnonymized(aProcess, aPath, aKind, aUnits, aAmount,
                                   aDescription)
   {
+    // Path might include an xmlns using http, which is safe to ignore.
+    let reducedPath = aPath.replace(XUL_NS, "");
+
     // Shouldn't get http: or https: in any paths.
-    if (aPath.includes('http:')) {
+    if (reducedPath.includes('http:')) {
         present.httpWhenAnonymized = aPath;
     }
 
     // file: URLs should have their path anonymized.
-    if (aPath.search('file:..[^<]') !== -1) {
+    if (reducedPath.search('file:..[^<]') !== -1) {
         present.unanonymizedFilePathWhenAnonymized = aPath;
     }
   }
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
   // Access the distinguished amounts (mgr.explicit et al.) just to make sure
@@ -386,9 +394,8 @@
     }
   }
 
   test_register_weak();
 
   ]]>
   </script>
 </window>
-
--- a/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
+++ b/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
@@ -23,20 +23,22 @@
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "jsapi.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsISupportsImpl.h"
 #include "nsNetUtil.h"
-#include "nsIFile.h"
+#include "nsPrintfCString.h"
 #include "prerror.h"
 #include "prio.h"
 #include "prtypes.h"
 
 namespace mozilla {
 namespace devtools {
 
 using namespace JS;
@@ -810,32 +812,65 @@ WriteHeapGraph(JSContext* cx,
 
 } // namespace devtools
 
 namespace dom {
 
 using namespace JS;
 using namespace devtools;
 
+static unsigned long
+msSinceProcessCreation(const TimeStamp& now)
+{
+  bool ignored;
+  auto duration = now - TimeStamp::ProcessCreation(ignored);
+  return (unsigned long) duration.ToMilliseconds();
+}
+
+// Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap
+// snapshots are serialized into.
+static already_AddRefed<nsIFile>
+createUniqueCoreDumpFile(ErrorResult& rv, const TimeStamp& now, nsAString& outFilePath)
+{
+  nsCOMPtr<nsIFile> file;
+  rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  auto ms = msSinceProcessCreation(now);
+  rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  rv = file->GetPath(outFilePath);
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  return file.forget();
+}
+
 /* static */ void
 ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
                                         JSContext* cx,
-                                        const nsAString& filePath,
                                         const HeapSnapshotBoundaries& boundaries,
+                                        nsAString& outFilePath,
                                         ErrorResult& rv)
 {
   auto start = TimeStamp::Now();
 
   bool wantNames = true;
   ZoneSet zones;
   uint32_t nodeCount = 0;
   uint32_t edgeCount = 0;
 
-  nsCOMPtr<nsIFile> file;
-  rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file));
+  nsCOMPtr<nsIFile> file = createUniqueCoreDumpFile(rv, start, outFilePath);
   if (NS_WARN_IF(rv.Failed()))
     return;
 
   nsCOMPtr<nsIOutputStream> outputStream;
   rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
                                    PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
                                    -1, 0);
   if (NS_WARN_IF(rv.Failed()))
--- a/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
+++ b/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
@@ -10,22 +10,16 @@ Bug 1024774 - Sanity test that we can ta
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
 <script>
 SimpleTest.waitForExplicitFinish();
 window.onload = function() {
   ok(ChromeUtils, "The ChromeUtils interface should be exposed in chrome windows.");
-
-  var file = Components.classes["@mozilla.org/file/directory_service;1"]
-    .getService(Components.interfaces.nsIProperties)
-    .get("CurWorkD", Components.interfaces.nsILocalFile);
-  file.append("core-dump.tmp");
-
-  ChromeUtils.saveHeapSnapshot(file.path, { runtime: true });
+  ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should save a heap snapshot and shouldn't throw.");
   SimpleTest.finish();
 };
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -101,23 +101,20 @@ function getFilePath(aName, aAllowMissin
 
   if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
     path = path.replace(/\//g, "\\");
   }
 
   return path;
 }
 
-function saveNewHeapSnapshot(fileName=`core-dump-${Math.random()}.tmp`) {
-  const filePath = getFilePath(fileName, true, true);
+function saveNewHeapSnapshot() {
+  const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(filePath, "Should get a file path to save the core dump to.");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
   ok(true, "Saved a heap snapshot to " + filePath);
-
   return filePath;
 }
 
 /**
  * Save a heap snapshot to the file with the given name in the current
  * directory, read it back as a HeapSnapshot instance, and then take a census of
  * the heap snapshot's serialized heap graph with the provided census options.
  *
@@ -130,26 +127,20 @@ function saveNewHeapSnapshot(fileName=`c
  *        the Debugger's debuggees. If null, serialize the whole heap graph.
  *
  * @param {String} fileName
  *        The file name to save the heap snapshot's core dump file to, within
  *        the current directory.
  *
  * @returns Census
  */
-function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined,
-                                       // Add the Math.random() so that parallel
-                                       // tests are less likely to mess with
-                                       // each other.
-                                       fileName="core-dump-" + (Math.random()) + ".tmp") {
-  const filePath = getFilePath(fileName, true, true);
+function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
+  const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
+  const filePath = ChromeUtils.saveHeapSnapshot(snapshotOptions);
   ok(filePath, "Should get a file path to save the core dump to.");
-
-  const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
-  ChromeUtils.saveHeapSnapshot(filePath, snapshotOptions);
   ok(true, "Should have saved a heap snapshot to " + filePath);
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should have read a heap snapshot back from " + filePath);
   ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
 
   equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
   return snapshot.takeCensus(censusOptions);
--- a/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js
@@ -1,26 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 console.log("Initializing worker.");
 
 self.onmessage = e => {
   console.log("Starting test.");
   try {
-    const { filePath } = e.data;
-
     ok(typeof ChromeUtils === "undefined",
        "Should not have access to ChromeUtils in a worker.");
     ok(ThreadSafeChromeUtils,
        "Should have access to ThreadSafeChromeUtils in a worker.");
     ok(HeapSnapshot,
        "Should have access to HeapSnapshot in a worker.");
 
-    ThreadSafeChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+    const filePath = ThreadSafeChromeUtils.saveHeapSnapshot({ globals: [this] });
     ok(true, "Should be able to save a snapshot.");
 
     const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath);
     ok(snapshot, "Should be able to read a heap snapshot");
     ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
   } catch (e) {
     ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
   } finally {
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
@@ -4,20 +4,17 @@
 // Test that we can read core dumps into HeapSnapshot instances.
 
 if (typeof Debugger != "function") {
   const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   addDebuggerToGlobal(this);
 }
 
 function run_test() {
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+  const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
   ok(true, "Should be able to save a snapshot.");
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should be able to read a heap snapshot");
   ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
 
   do_test_finished();
 }
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
@@ -20,20 +20,17 @@ function run_test() {
   debuggee.eval("this.objects = []");
   for (let i = 0; i < 100; i++) {
     debuggee.eval("this.objects.push({})");
   }
 
   // Now save a snapshot that will include the allocation stacks and read it
   // back again.
 
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
+  const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should be able to save a snapshot.");
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should be able to read a heap snapshot");
   ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
 
   do_test_finished();
 }
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
@@ -1,19 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that we can read core dumps into HeapSnapshot instances in a worker.
 
 add_task(function* () {
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
   const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js");
-  worker.postMessage({ filePath });
+  worker.postMessage({});
 
   let assertionCount = 0;
   worker.onmessage = e => {
     if (e.data.type !== "assertion") {
       return;
     }
 
     ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
@@ -6,94 +6,77 @@
 if (typeof Debugger != "function") {
   const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   addDebuggerToGlobal(this);
 }
 
 function run_test() {
   ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
 
-  let filePath = getFilePath("core-dump.tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  testBadParameters(filePath);
-  testGoodParameters(filePath);
+  testBadParameters();
+  testGoodParameters();
 
   do_test_finished();
 }
 
-function testBadParameters(filePath) {
+function testBadParameters() {
   throws(() => ChromeUtils.saveHeapSnapshot(),
          "Should throw if arguments aren't passed in.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(Object.create(null),
-                                            { runtime: true }),
-         "Should throw if the filePath is not coercible to string.");
-
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            null),
+  throws(() => ChromeUtils.saveHeapSnapshot(null),
          "Should throw if boundaries isn't an object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            {}),
+  throws(() => ChromeUtils.saveHeapSnapshot({}),
          "Should throw if the boundaries object doesn't have any properties.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { runtime: true,
+  throws(() => ChromeUtils.saveHeapSnapshot({ runtime: true,
                                               globals: [this] }),
          "Should throw if the boundaries object has more than one property.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { debugger: {} }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ debugger: {} }),
          "Should throw if the debuggees object is not a Debugger object");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: [{}] }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: [{}] }),
          "Should throw if the globals array contains non-global objects.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { runtime: false }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ runtime: false }),
          "Should throw if runtime is supplied and is not true.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: null }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: null }),
          "Should throw if globals is not an object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: {} }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: {} }),
          "Should throw if globals is not an array.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { debugger: Debugger.prototype }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }),
          "Should throw if debugger is the Debugger.prototype object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { get globals() { return [this]; } }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ get globals() { return [this]; } }),
          "Should throw if boundaries property is a getter.");
 }
 
 const makeNewSandbox = () =>
   Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')());
 
-function testGoodParameters(filePath) {
+function testGoodParameters() {
   let sandbox = makeNewSandbox();
   let dbg = new Debugger(sandbox);
 
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot for a debuggee global.");
 
   dbg = new Debugger;
   let sandboxes = Array(10).fill(null).map(makeNewSandbox);
   sandboxes.forEach(sb => dbg.addDebuggee(sb));
 
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot for many debuggee globals.");
 
   dbg = new Debugger;
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot with no debuggee globals.");
 
-  ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+  ChromeUtils.saveHeapSnapshot({ globals: [this] });
   ok(true, "Should be able to save a snapshot for a specific global.");
 
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
+  ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should be able to save a snapshot of the full runtime.");
 }
--- a/toolkit/devtools/server/actors/call-watcher.js
+++ b/toolkit/devtools/server/actors/call-watcher.js
@@ -50,31 +50,34 @@ let FunctionCallActor = protocol.ActorCl
    *        The object owning the function when it was called.
    *        For example, in `foo.bar()`, the caller is `foo`.
    * @param number type
    *        Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER.
    * @param string name
    *        The called function's name.
    * @param array stack
    *        The called function's stack, as a list of { name, file, line } objects.
+   * @param number timestamp
+   *        The timestamp of draw-related functions
    * @param array args
    *        The called function's arguments.
    * @param any result
    *        The value returned by the function call.
    * @param boolean holdWeak
    *        Determines whether or not FunctionCallActor stores a weak reference
    *        to the underlying objects.
    */
-  initialize: function(conn, [window, global, caller, type, name, stack, args, result], holdWeak) {
+  initialize: function(conn, [window, global, caller, type, name, stack, timestamp, args, result], holdWeak) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.details = {
       type: type,
       name: name,
       stack: stack,
+      timestamp: timestamp
     };
 
     // Store a weak reference to all objects so we don't
     // prevent natural GC if `holdWeak` was passed into
     // setup as truthy. Used in the Web Audio Editor.
     if (holdWeak) {
       let weakRefs = {
         window: Cu.getWeakReference(window),
@@ -82,25 +85,27 @@ let FunctionCallActor = protocol.ActorCl
         result: Cu.getWeakReference(result),
         args: Cu.getWeakReference(args)
       };
 
       Object.defineProperties(this.details, {
         window: { get: () => weakRefs.window.get() },
         caller: { get: () => weakRefs.caller.get() },
         result: { get: () => weakRefs.result.get() },
-        args: { get: () => weakRefs.args.get() }
+        args: { get: () => weakRefs.args.get() },
+        timestamp: { get: () => weakRefs.timestamp.get() },
       });
     }
     // Otherwise, hold strong references to the objects.
     else {
       this.details.window = window;
       this.details.caller = caller;
       this.details.result = result;
       this.details.args = args;
+      this.details.timestamp = timestamp;
     }
 
     this.meta = {
       global: -1,
       previews: { caller: "", args: "" }
     };
 
     if (global == "WebGLRenderingContext") {
@@ -123,27 +128,28 @@ let FunctionCallActor = protocol.ActorCl
    */
   form: function() {
     return {
       actor: this.actorID,
       type: this.details.type,
       name: this.details.name,
       file: this.details.stack[0].file,
       line: this.details.stack[0].line,
+      timestamp: this.details.timestamp,
       callerPreview: this.meta.previews.caller,
       argsPreview: this.meta.previews.args
     };
   },
 
   /**
    * Gets more information about this function call, which is not necessarily
    * available on the Front instance.
    */
   getDetails: method(function() {
-    let { type, name, stack } = this.details;
+    let { type, name, stack, timestamp } = this.details;
 
     // Since not all calls on the stack have corresponding owner files (e.g.
     // callbacks of a requestAnimationFrame etc.), there's no benefit in
     // returning them, as the user can't jump to the Debugger from them.
     for (let i = stack.length - 1;;) {
       if (stack[i].file) {
         break;
       }
@@ -151,17 +157,18 @@ let FunctionCallActor = protocol.ActorCl
       i--;
     }
 
     // XXX: Use grips for objects and serialize them properly, in order
     // to add the function's caller, arguments and return value. Bug 978957.
     return {
       type: type,
       name: name,
-      stack: stack
+      stack: stack,
+      timestamp: timestamp
     };
   }, {
     response: { info: RetVal("call-details") }
   }),
 
   /**
    * Serializes the caller's name so that it can be easily be transferred
    * as a string, but still be useful when displayed in a potential UI.
@@ -238,16 +245,17 @@ let FunctionCallFront = protocol.FrontCl
    * to avoid extra roundtrips.
    */
   form: function(form) {
     this.actorID = form.actor;
     this.type = form.type;
     this.name = form.name;
     this.file = form.file;
     this.line = form.line;
+    this.timestamp = form.timestamp;
     this.callerPreview = form.callerPreview;
     this.argsPreview = form.argsPreview;
   }
 });
 
 /**
  * This actor observes function calls on certain objects or globals.
  */
@@ -329,16 +337,23 @@ let CallWatcherActor = exports.CallWatch
    */
   isRecording: method(function() {
     return this._recording;
   }, {
     response: RetVal("boolean")
   }),
 
   /**
+   * Initialize frame start timestamp for measuring
+   */
+  initFrameStartTimestamp: method(function() {
+    this._frameStartTimestamp = this.tabActor.window.performance.now();
+  }),
+
+  /**
    * Starts recording function calls.
    */
   resumeRecording: method(function() {
     this._recording = true;
   }),
 
   /**
    * Stops recording function calls.
@@ -419,19 +434,20 @@ let CallWatcherActor = exports.CallWatch
         let result;
         try {
           result = Cu.waiveXrays(originalFunc.apply(this, args));
         } catch (e) {
           throw createContentError(e, unwrappedWindow);
         }
 
         if (self._recording) {
+          let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
           let stack = getStack(name);
           let type = CallWatcherFront.METHOD_FUNCTION;
-          callback(unwrappedWindow, global, this, type, name, stack, args, result);
+          callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result);
         }
         return result;
       }, target, { defineAs: name });
 
       Object.defineProperty(target, name, {
         configurable: descriptor.configurable,
         enumerable: descriptor.enumerable,
         writable: true
@@ -448,30 +464,32 @@ let CallWatcherActor = exports.CallWatch
       let originalSetter = Cu.unwaiveXrays(target.__lookupSetter__(name));
 
       Object.defineProperty(target, name, {
         get: function(...args) {
           if (!originalGetter) return undefined;
           let result = Cu.waiveXrays(originalGetter.apply(this, args));
 
           if (self._recording) {
+            let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
             let stack = getStack(name);
             let type = CallWatcherFront.GETTER_FUNCTION;
-            callback(unwrappedWindow, global, this, type, name, stack, args, result);
+            callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result);
           }
           return result;
         },
         set: function(...args) {
           if (!originalSetter) return;
           originalSetter.apply(this, args);
 
           if (self._recording) {
+            let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
             let stack = getStack(name);
             let type = CallWatcherFront.SETTER_FUNCTION;
-            callback(unwrappedWindow, global, this, type, name, stack, args, undefined);
+            callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, undefined);
           }
         },
         configurable: descriptor.configurable,
         enumerable: descriptor.enumerable
       });
     }
 
     /**
--- a/toolkit/devtools/server/actors/canvas.js
+++ b/toolkit/devtools/server/actors/canvas.js
@@ -311,16 +311,17 @@ let CanvasActor = exports.CanvasActor = 
    */
   recordAnimationFrame: method(function() {
     if (this._callWatcher.isRecording()) {
       return this._currentAnimationFrameSnapshot.promise;
     }
 
     this._recordingContainsDrawCall = false;
     this._callWatcher.eraseRecording();
+    this._callWatcher.initFrameStartTimestamp();
     this._callWatcher.resumeRecording();
 
     let deferred = this._currentAnimationFrameSnapshot = promise.defer();
     return deferred.promise;
   }, {
     response: { snapshot: RetVal("nullable:frame-snapshot") }
   }),
 
--- a/toolkit/devtools/shared/memory.js
+++ b/toolkit/devtools/shared/memory.js
@@ -134,18 +134,17 @@ let Memory = exports.Memory = Class({
 
   /**
    * Save a heap snapshot scoped to the current debuggees' portion of the heap
    * graph.
    *
    * @returns {String} The snapshot id.
    */
   saveHeapSnapshot: expectState("attached", function () {
-    const path = HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
-    ThreadSafeChromeUtils.saveHeapSnapshot(path, { debugger: this.dbg });
+    const path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg });
     return HeapSnapshotFileUtils.getSnapshotIdFromPath(path);
   }, "saveHeapSnapshot"),
 
   /**
    * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
    * more information.
    */
   takeCensus: expectState("attached", function() {
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -2113,18 +2113,20 @@ this.XPIDatabaseReconcile = {
     let locationAddonMap = currentAddons.get(hideLocation);
     if (locationAddonMap) {
       for (let addon of locationAddonMap.values()) {
         addon.visible = false;
         addon.active = false;
       }
     }
 
-    // None of the active add-ons match the selected theme, enable the default.
-    if (!sawActiveTheme) {
+    // If a custom theme is selected and it wasn't seen in the new list of
+    // active add-ons then enable the default theme
+    if (XPIProvider.selectedSkin != XPIProvider.defaultSkin && !sawActiveTheme) {
+      logger.info("Didn't see selected skin " + XPIProvider.selectedSkin);
       XPIProvider.enableDefaultTheme();
     }
 
     // Finally update XPIStates to match everything
     for (let [locationName, locationAddonMap] of currentAddons) {
       for (let [id, addon] of locationAddonMap) {
         let xpiState = XPIStates.getAddon(locationName, id);
         xpiState.syncWithDB(addon);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
@@ -1084,12 +1084,56 @@ function run_test_21() {
         "theme1@tests.mozilla.org": [
           "onDisabling"
         ]
       });
 
       p1.userDisabled = false;
       ensure_test_completed();
 
-      end_test();
+      run_test_22();
     });
   }));
 }
+
+// Detecting a new add-on during the startup file check should not disable an
+// active lightweight theme
+function run_test_22() {
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
+                               "1@personas.mozilla.org"], function([d, p1]) {
+    do_check_true(d.userDisabled);
+    do_check_false(d.appDisabled);
+    do_check_false(d.isActive);
+
+    do_check_false(p1.userDisabled);
+    do_check_false(p1.appDisabled);
+    do_check_true(p1.isActive);
+
+    writeInstallRDFForExtension({
+      id: "theme3@tests.mozilla.org",
+      version: "1.0",
+      name: "Test 3",
+      internalName: "theme3/1.0",
+      targetApplications: [{
+        id: "xpcshell@tests.mozilla.org",
+        minVersion: "1",
+        maxVersion: "2"
+      }]
+    }, profileDir);
+
+    restartManager();
+
+    AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
+                                 "1@personas.mozilla.org"], function([d, p1]) {
+      do_check_true(d.userDisabled);
+      do_check_false(d.appDisabled);
+      do_check_false(d.isActive);
+
+      do_check_false(p1.userDisabled);
+      do_check_false(p1.appDisabled);
+      do_check_true(p1.isActive);
+
+      end_test();
+    });
+  });
+}