Bug 1289974 part 1: Device selection for presentation API on Desktop; r=mconley
authorChun-Min Chang <chun.m.chang@gmail.com>
Tue, 15 Nov 2016 11:07:09 +0800
changeset 367886 369e16cb8e679f3bc2915efd933a73bdb6853b21
parent 367885 8cfe56f5b5d4f76163a9e8be6eaaf045926fb15a
child 367887 d17322d95e8793ac606aaae9b4159567b6890e48
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1289974
milestone53.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
Bug 1289974 part 1: Device selection for presentation API on Desktop; r=mconley MozReview-Commit-ID: KKT8xsafuAQ
browser/extensions/moz.build
browser/extensions/presentation/bootstrap.js
browser/extensions/presentation/content/PresentationDevicePrompt.jsm
browser/extensions/presentation/install.rdf.in
browser/extensions/presentation/jar.mn
browser/extensions/presentation/locale/en-US/presentation.properties
browser/extensions/presentation/locale/jar.mn
browser/extensions/presentation/locale/moz.build
browser/extensions/presentation/moz.build
browser/extensions/presentation/skin/shared/link.svg
browser/locales/Makefile.in
testing/talos/talos/xtalos/xperf_whitelist.json
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -12,9 +12,10 @@ DIRS += [
     'webcompat',
 ]
 
 # Only include the following system add-ons if building Aurora or Nightly
 if 'a' in CONFIG['GRE_MILESTONE']:
     DIRS += [
         'flyweb',
         'formautofill',
+        'presentation',
     ]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/bootstrap.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PRESENTATION_DEVICE_PROMPT_PATH =
+  "chrome://presentation/content/PresentationDevicePrompt.jsm";
+
+function log(aMsg) {
+  // dump("@ Presentation: " + aMsg + "\n");
+}
+
+function install(aData, aReason) {
+}
+
+function uninstall(aData, aReason) {
+}
+
+function startup(aData, aReason) {
+  log("startup");
+  Presentation.init();
+}
+
+function shutdown(aData, aReason) {
+  log("shutdown");
+  Presentation.uninit();
+}
+
+// Register/unregister a constructor as a factory.
+function Factory() {}
+Factory.prototype = {
+  register: function(targetConstructor) {
+    let proto = targetConstructor.prototype;
+    this._classID = proto.classID;
+
+    let factory = XPCOMUtils._getFactory(targetConstructor);
+    this._factory = factory;
+
+    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+    registrar.registerFactory(proto.classID, proto.classDescription,
+                              proto.contractID, factory);
+  },
+
+  unregister: function() {
+    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+    registrar.unregisterFactory(this._classID, this._factory);
+    this._factory = null;
+    this._classID = null;
+  },
+};
+
+var Presentation = {
+  // PUBLIC APIs
+  init: function() {
+    log("init");
+    // Register PresentationDevicePrompt into a XPCOM component.
+    Cu.import(PRESENTATION_DEVICE_PROMPT_PATH);
+    this._register();
+  },
+
+  uninit: function() {
+    log("uninit");
+    // Unregister PresentationDevicePrompt XPCOM component.
+    this._unregister();
+    Cu.unload(PRESENTATION_DEVICE_PROMPT_PATH);
+  },
+
+  // PRIVATE APIs
+  _register: function() {
+    log("_register");
+    this._devicePromptFactory = new Factory();
+    this._devicePromptFactory.register(PresentationDevicePrompt);
+  },
+
+  _unregister: function() {
+    log("_unregister");
+    this._devicePromptFactory.unregister();
+    delete this._devicePromptFactory;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/content/PresentationDevicePrompt.jsm
@@ -0,0 +1,254 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+/*
+ * This is the implementation of nsIPresentationDevicePrompt XPCOM.
+ * It will be registered into a XPCOM component by Presentation.jsm.
+ *
+ * This component will prompt a device selection UI for users to choose which
+ * devices they want to connect, when PresentationRequest is started.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["PresentationDevicePrompt"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// An string bundle for localization.
+XPCOMUtils.defineLazyGetter(this, "Strings", function() {
+  return Services.strings.createBundle("chrome://presentation/locale/presentation.properties");
+});
+// To generate a device selection prompt.
+XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
+                                        "resource:///modules/PermissionUI.jsm");
+/*
+ * Utils
+ */
+function log(aMsg) {
+  // Prefix is useful to grep log.
+  // dump("@ PresentationDevicePrompt: " + aMsg + "\n");
+}
+
+function GetString(aName) {
+  return Strings.GetStringFromName(aName);
+}
+
+/*
+ * Device Selection UI
+ */
+const kNotificationId = "presentation-device-selection";
+const kNotificationPopupIcon = "chrome://presentation-shared/skin/link.svg";
+
+// There is no dependancy between kNotificationId and kNotificationAnchorId,
+// so it's NOT necessary to name them by same prefix
+// (e.g., presentation-device-selection-notification-icon).
+const kNotificationAnchorId = "presentation-device-notification-icon";
+const kNotificationAnchorIcon = "chrome://presentation-shared/skin/link.svg";
+
+// This will insert our own popupnotification content with the device list
+// into the displayed popupnotification element.
+// PopupNotifications.jsm will automatically generate a popupnotification
+// element whose id is <notification id> + "-notification" and show it,
+// so kPopupNotificationId must be kNotificationId + "-notification".
+// Read more detail in PopupNotifications._refreshPanel.
+const kPopupNotificationId = kNotificationId + "-notification";
+
+function PresentationPermissionPrompt(aRequest, aDevices) {
+  this.request = aRequest;
+  this._isResponded = false;
+  this._devices = aDevices;
+}
+
+PresentationPermissionPrompt.prototype = {
+  __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+  // PUBLIC APIs
+  get browser() {
+    return this.request.chromeEventHandler;
+  },
+  get principal() {
+    return this.request.principal;
+  },
+  get popupOptions() {
+    return {
+      hideNotNow: true,
+      removeOnDismissal: true,
+      popupIconURL: kNotificationPopupIcon, // Icon shown on prompt content
+      eventCallback: (aTopic, aNewBrowser) => {
+        log("eventCallback: " + aTopic);
+        let handler = {
+          // dismissed: () => { // Won't be fired if removeOnDismissal is true.
+          //   log("Dismissed by user. Cancel the request.");
+          // },
+          removed: () => {
+            log("Prompt is removed.");
+            if (!this._isResponded) {
+              log("Dismissed by user. Cancel the request.");
+              this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
+            }
+          },
+          showing: () => {
+            log("Prompt is showing.");
+            // We cannot insert the device list at "showing" phase because
+            // the popupnotification content whose id is kPopupNotificationId
+            // is not generated yet.
+          },
+          shown: () => {
+            log("Prompt is shown.");
+            // Insert device selection list into popupnotification element.
+            this._createPopupContent();
+          },
+        };
+
+        // Call the handler for Notification events.
+        handler[aTopic]();
+      },
+    };
+  },
+  get notificationID() {
+    return kNotificationId;
+  },
+  get anchorID() {
+    let chromeDoc = this.browser.ownerDocument;
+    let anchor = chromeDoc.getElementById(kNotificationAnchorId);
+    if (!anchor) {
+      let notificationPopupBox =
+        chromeDoc.getElementById("notification-popup-box");
+      // Icon shown on URL bar
+      let notificationIcon = chromeDoc.createElement("image");
+      notificationIcon.id = kNotificationAnchorId;
+      notificationIcon.setAttribute("src", kNotificationAnchorIcon);
+      notificationIcon.classList.add("notification-anchor-icon");
+      notificationIcon.setAttribute("role", "button");
+      notificationIcon.setAttribute("tooltiptext",
+                                    GetString("presentation.urlbar.tooltiptext"));
+      notificationIcon.style.filter = "url('chrome://browser/skin/filters.svg#fill')";
+      notificationIcon.style.fill = "currentcolor";
+      notificationIcon.style.opacity = "0.4";
+      notificationPopupBox.appendChild(notificationIcon);
+    }
+
+    return kNotificationAnchorId;
+  },
+  get message() {
+    return GetString("presentation.message", this._domainName);
+  },
+  get promptActions() {
+    return [{
+      label: GetString("presentation.deviceprompt.select.label"),
+      accessKey: GetString("presentation.deviceprompt.select.accessKey"),
+      callback: () => {
+        log("Select");
+        this._isResponded = true;
+        if (!this._listbox || !this._devices.length) {
+          log("No device can be selected!");
+          this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
+          return;
+        }
+        let device = this._devices[this._listbox.selectedIndex];
+        this.request.select(device);
+        log("device: " + device.name + "(" + device.id + ") is selected!");
+      },
+    }, {
+      label: GetString("presentation.deviceprompt.cancel.label"),
+      accessKey: GetString("presentation.deviceprompt.cancel.accessKey"),
+      callback: () => {
+        log("Cancel selection.");
+        this._isResponded = true;
+        this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
+      },
+      dismiss: true, // For hideNotNow.
+    }];
+  },
+  // PRIVATE APIs
+  get _domainName() {
+    if (this.principal.URI instanceof Ci.nsIFileURL) {
+      return this.principal.URI.path.split('/')[1];
+    }
+    return this.principal.URI.hostPort;
+  },
+  _createPopupContent: function() {
+    log("_createPopupContent");
+
+    if (!this._devices.length) {
+      log("No available devices can be listed!");
+      return;
+    }
+
+    let chromeDoc = this.browser.ownerDocument;
+
+    let popupnotification = chromeDoc.getElementById(kPopupNotificationId);
+    if (!popupnotification) {
+      log("No available popupnotification element to be inserted!");
+      return;
+    }
+
+    let popupnotificationcontent =
+      chromeDoc.createElement("popupnotificationcontent");
+
+    this._listbox = chromeDoc.createElement("richlistbox");
+    this._listbox.setAttribute("flex", "1");
+    this._devices.forEach((device) => {
+      let listitem = chromeDoc.createElement("richlistitem");
+      let label = chromeDoc.createElement("label");
+      label.setAttribute("value", device.name);
+      listitem.appendChild(label);
+      this._listbox.appendChild(listitem);
+    });
+
+    popupnotificationcontent.appendChild(this._listbox);
+    popupnotification.appendChild(popupnotificationcontent);
+  },
+};
+
+
+/*
+ * nsIPresentationDevicePrompt
+ */
+// For XPCOM registration
+const PRESENTATIONDEVICEPROMPT_CONTRACTID = "@mozilla.org/presentation-device/prompt;1";
+const PRESENTATIONDEVICEPROMPT_CID        = Components.ID("{388bd149-c919-4a43-b646-d7ec57877689}");
+
+function PresentationDevicePrompt() {}
+
+PresentationDevicePrompt.prototype = {
+  // properties required for XPCOM registration:
+  classID: PRESENTATIONDEVICEPROMPT_CID,
+  classDescription: "Presentation API Device Prompt",
+  contractID: PRESENTATIONDEVICEPROMPT_CONTRACTID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt]),
+
+  // This will be fired when window.PresentationRequest(URL).start() is called.
+  promptDeviceSelection: function(aRequest) {
+    log("promptDeviceSelection");
+
+    // Cancel request if no available device.
+    let devices = this._loadDevices();
+    if (!devices.length) {
+      log("No available device.");
+      aRequest.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+
+    // Show the prompt to users.
+    let promptUI = new PresentationPermissionPrompt(aRequest, devices);
+    promptUI.prompt();
+  },
+  _loadDevices: function() {
+    let deviceManager = Cc["@mozilla.org/presentation-device/manager;1"]
+                        .getService(Ci.nsIPresentationDeviceManager);
+    let devices = deviceManager.getAvailableDevices().QueryInterface(Ci.nsIArray);
+    let list = [];
+    for (let i = 0; i < devices.length; i++) {
+      let device = devices.queryElementAt(i, Ci.nsIPresentationDevice);
+      list.push(device);
+    }
+
+    return list;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/install.rdf.in
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>presentation@mozilla.org</em:id>
+    <em:version>1.0.0</em:version>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+    <!-- Target Application this theme can install into,
+        with minimum and maximum supported versions. -->
+    <em:targetApplication>
+      <Description>
+        <!-- Firefox GUID -->
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Presentation</em:name>
+    <em:description>Discover nearby devices in the browser</em:description>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/jar.mn
@@ -0,0 +1,5 @@
+[features/presentation@mozilla.org] chrome.jar:
+% content presentation %content/
+  content/  (content/*)
+% skin presentation-shared classic/1.0 %skin/shared/
+  skin/  (skin/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/locale/en-US/presentation.properties
@@ -0,0 +1,6 @@
+presentation.message=Select one device to send the content.
+presentation.urlbar.tooltiptext=View the device-selection request
+presentation.deviceprompt.select.label=Send
+presentation.deviceprompt.select.accessKey=S
+presentation.deviceprompt.cancel.label=Cancel
+presentation.deviceprompt.cancel.accessKey=C
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/locale/jar.mn
@@ -0,0 +1,8 @@
+#filter substitution
+# 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/.
+
+[features/presentation@mozilla.org] @AB_CD@.jar:
+% locale presentation @AB_CD@ %locale/@AB_CD@/
+  locale/@AB_CD@/                    (en-US/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/locale/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/moz.build
@@ -0,0 +1,14 @@
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+DIRS += ['locale']
+
+FINAL_TARGET_FILES.features['presentation@mozilla.org'] += [
+  'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['presentation@mozilla.org'] += [
+  'install.rdf.in'
+]
+
+JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/presentation/skin/shared/link.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray" x="0px" y="0px" width="32px"
+	 height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<g id="layer_2">
+</g>
+<g id="layer_1">
+	<g>
+		<g>
+			<path fill-rule="evenodd" clip-rule="evenodd" d="M16.968,9.895c3.293,0,6.117,1.995,7.338,4.841l1.603-1.216
+				c-1.628-3.3-4.994-5.591-8.923-5.591c-3.942,0-7.319,2.305-8.94,5.624l1.594,1.166C10.865,11.883,13.682,9.895,16.968,9.895z
+				 M17.135,13.964c1.948,0,3.566,1.397,3.917,3.244l1.779-1.35c-0.849-2.276-3.023-3.904-5.595-3.904
+				c-2.669,0-4.904,1.758-5.677,4.171l1.647,1.205C13.509,15.424,15.145,13.964,17.135,13.964z M8.756,16.025H1.833
+				c-0.737,0-1.729,0.598-1.729,1.335v11.271c0,0.738,0.596,1.335,1.334,1.335h7.318c0.738,0,1.336-0.597,1.336-1.335V17.36
+				C10.092,16.623,9.494,16.025,8.756,16.025z M8.113,27.472c0,0.299-0.243,0.541-0.541,0.541H2.599
+				c-0.298,0-0.541-0.242-0.541-0.541v-8.949c0-0.299,0.243-0.541,0.541-0.541h4.973c0.298,0,0.541,0.242,0.541,0.541V27.472z
+				 M15.246,17.992c0,1.064,0.862,1.926,1.926,1.926c1.064,0,1.926-0.862,1.926-1.926c0-1.064-0.862-1.926-1.926-1.926
+				C16.108,16.066,15.246,16.929,15.246,17.992z M29.962,2.034H4.011c-1.102,0-1.996,0.894-1.996,1.996v10.008h4.042V5.937
+				c0-1.102,0.894-1.996,1.996-1.996h17.973c1.103,0,1.996,0.894,1.996,1.996v16.075c0,1.103-0.894,1.996-1.996,1.996H12.089v1.918
+				h17.873c1.102,0,1.996-0.894,1.996-1.996V4.03C31.958,2.927,31.064,2.034,29.962,2.034z"/>
+		</g>
+	</g>
+</g>
+</svg>
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -95,16 +95,17 @@ searchplugins:: $(list-json)
 DEFINES += -DBOOKMARKS_INCLUDE_DIR=$(dir $(call MERGE_FILE,profile/bookmarks.inc))
 
 libs-%:
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
+	@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 
 repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
 repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
--- a/testing/talos/talos/xtalos/xperf_whitelist.json
+++ b/testing/talos/talos/xtalos/xperf_whitelist.json
@@ -1,41 +1,42 @@
-{"{firefox}\\Crash Reports\\{time}": {"ignore": true}, 
- "C:\\$Mft": {"ignore": true}, 
- "C:\\$Extend\\$UsnJrnl:$J": {"ignore": true}, 
- "C:\\Windows\\Prefetch\\{prefetch}.pf": {"ignore": true}, 
+{"{firefox}\\Crash Reports\\{time}": {"ignore": true},
+ "C:\\$Mft": {"ignore": true},
+ "C:\\$Extend\\$UsnJrnl:$J": {"ignore": true},
+ "C:\\Windows\\Prefetch\\{prefetch}.pf": {"ignore": true},
  "C:\\$Secure": {"ignore": true},
  "C:\\$logfile": {"ignore": true},
  "{firefox}\\omni.ja": {"mincount": 0, "maxcount": 46, "minbytes": 0, "maxbytes": 3014656},
  "{firefox}\\browser\\omni.ja": {"mincount": 0, "maxcount": 28, "minbytes": 0, "maxbytes": 1835008},
  "{firefox}\\browser\\features\\aushelper@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\e10srollout@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\flyweb@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\formautofill@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\loop@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\firefox@getpocket.com.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
+ "{firefox}\\browser\\features\\presentation@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\webcompat@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{talos}\\tests\\tp5n\\tp5n.manifest": {"mincount": 0, "maxcount": 8, "minbytes": 0, "maxbytes": 32786},
  "{talos}\\talos\\tests\\tp5n\\tp5n.manifest": {"mincount": 0, "maxcount": 8, "minbytes": 0, "maxbytes": 32786},
  "{talos}\\tests\\tp5n\\tp5n.manifest.develop": {"mincount": 0, "maxcount": 8, "minbytes": 0, "maxbytes": 32786},
  "{talos}\\talos\\tests\\tp5n\\tp5n.manifest.develop": {"mincount": 0, "maxcount": 8, "minbytes": 0, "maxbytes": 32786},
  "{profile}\\localstore.rdf": {"mincount": 2, "maxcount": 2, "minbytes": 8192, "maxbytes": 8192},
  "{firefox}\\dependentlibs.list": {"mincount": 4, "maxcount": 4, "minbytes": 16384, "maxbytes": 16384},
- "{profile}\\content-prefs.sqlite": {"mincount": 6, "maxcount": 6, "minbytes": 65768, "maxbytes": 65768}, 
+ "{profile}\\content-prefs.sqlite": {"mincount": 6, "maxcount": 6, "minbytes": 65768, "maxbytes": 65768},
  "{profile}\\extensions.ini": {"mincount": 2, "maxcount": 2, "minbytes": 8192, "maxbytes": 8192},
  "{profile}\\extensions\\pageloader@mozilla.org\\chrome.manifest": {"mincount": 2, "maxcount": 2, "minbytes": 600, "maxbytes": 600},
  "{profile}\\extensions\\talos-powers@mozilla.org\\chrome.manifest": {"mincount": 2, "maxcount": 2, "minbytes": 600, "maxbytes": 600},
  "{profile}\\extensions\\talos-powers@mozilla.org\\chrome\\talos-powers-content.js": {"mincount": 2, "maxcount": 2, "minbytes": 2000, "maxbytes": 2000},
  "{profile}\\mimetypes.rdf": {"mincount": 2, "maxcount": 2, "minbytes": 8192, "maxbytes": 8192},
  "{firefox}\\profiles.ini": {"mincount": 2, "maxcount": 2, "minbytes": 8192, "maxbytes": 8192},
  "c:\\program files\\nvidia corporation\\3d vision\\nvstereoapii.dll": {"mincount": 0, "maxcount": 4, "minbytes": 2, "maxbytes": 33792},
  "c:\\programdata\\nvidia corporation\\drs\\nvdrssel.bin": {"mincount": 2, "maxcount": 2, "minbytes": 2, "maxbytes": 2},
  "c:\\programdata\\nvidia corporation\\drs\\nvapptimestamps": {"mincount": 22, "maxcount": 22, "minbytes": 704, "maxbytes": 704},
  "{firefox}\\browser\\components\\components.manifest": {"mincount": 2, "maxcount": 2, "minbytes": 68, "maxbytes": 68},
- "{profile}\\places.sqlite": {"mincount": 8, "maxcount": 8, "minbytes": 196808, "maxbytes": 196808}, 
+ "{profile}\\places.sqlite": {"mincount": 8, "maxcount": 8, "minbytes": 196808, "maxbytes": 196808},
  "{profile}\\pluginreg.dat": {"mincount": 2, "maxcount": 2, "minbytes": 1892, "maxbytes": 1892},
  "{firefox}\\defaults\\pref\\channel-prefs.js": {"mincount": 4, "maxcount": 4, "minbytes": 1432, "maxbytes": 1432},
  "c:\\windows\\system32\\dwrite.dll": {"mincount": 4, "maxcount": 4, "minbytes": 16384, "maxbytes": 90112},
  "c:\\users\\desktop.ini": {"mincount": 2, "maxcount": 2, "minbytes": 352, "maxbytes": 352},
  "{desktop}\\desktop.ini": {"mincount": 6, "maxcount": 6, "minbytes": 1692, "maxbytes": 1692},
  "c:\\windows\\fonts\\staticcache.dat": {"mincount": 2, "maxcount": 2, "minbytes": 120, "maxbytes": 120},
  "c:\\windows\\system32\\spool\\drivers\\color\\srgb color space profile.icm": {"mincount": 2, "maxcount": 2, "minbytes": 8192, "maxbytes": 8192},
  "{profile}\\search.json": {"mincount": 12, "maxcount": 12, "minbytes": 33350, "maxbytes": 33350},