Bug 1595328 - Add browser.experiments.urlbar.lastBrowserUpdateDate webextension experiment API. r=mak,mixedpuppy CLOSED TREE
authorDrew Willcoxon <adw@mozilla.com>
Thu, 14 Nov 2019 20:00:46 +0000
changeset 502111 409ebe45687765c849936626fbc7d95fcfc17f6d
parent 502110 17db3abeba1a0ee39f3887279ff770c5f04f6313
child 502112 3322c8c93b02a7e848951a454a5c94f6d4704a74
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, mixedpuppy
bugs1595328
milestone72.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 1595328 - Add browser.experiments.urlbar.lastBrowserUpdateDate webextension experiment API. r=mak,mixedpuppy CLOSED TREE Differential Revision: https://phabricator.services.mozilla.com/D52948
browser/components/urlbar/tests/ext/api.js
browser/components/urlbar/tests/ext/browser/browser.ini
browser/components/urlbar/tests/ext/browser/browser_ext_urlbar_lastBrowserUpdateDate.js
browser/components/urlbar/tests/ext/schema.json
--- a/browser/components/urlbar/tests/ext/api.js
+++ b/browser/components/urlbar/tests/ext/api.js
@@ -1,18 +1,20 @@
 /* global ExtensionAPI */
 
 "use strict";
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  ProfileAge: "resource://gre/modules/ProfileAge.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "updateService",
   "@mozilla.org/updates/update-service;1",
   "nsIApplicationUpdateService"
@@ -101,13 +103,26 @@ this.experiments_urlbar = class extends 
               return true;
             }
             // If the state is pending and there is an error, staging failed and
             // Firefox can be restarted to apply the update without staging.
             let update = updateManager.activeUpdate;
             let errorCode = update ? update.errorCode : 0;
             return updateStateIs("pending") && errorCode != 0;
           },
+
+          async lastBrowserUpdateDate() {
+            // Get the newest update in the update history.  This isn't perfect
+            // because these dates are when updates are applied, not when the
+            // user restarts with the update.  See bug 1595328.
+            if (updateManager.updateCount) {
+              let update = updateManager.getUpdateAt(0);
+              return update.installDate;
+            }
+            // Fall back to the profile age.
+            let age = await ProfileAge();
+            return (await age.firstUse) || age.created;
+          },
         },
       },
     };
   }
 };
--- a/browser/components/urlbar/tests/ext/browser/browser.ini
+++ b/browser/components/urlbar/tests/ext/browser/browser.ini
@@ -6,8 +6,9 @@
 support-files =
   ../../browser/head-common.js
   ../api.js
   ../schema.json
   head.js
 
 [browser_ext_urlbar_isBrowserShowingNotification.js]
 [browser_ext_urlbar_isBrowserUpdateReadyToInstall.js]
+[browser_ext_urlbar_lastBrowserUpdateDate.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/urlbar/tests/ext/browser/browser_ext_urlbar_lastBrowserUpdateDate.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global browser */
+
+// This tests the browser.experiments.urlbar.lastBrowserUpdateDate WebExtension
+// Experiment API.  The parts related to the updates.xml file are adapted from
+// browser_policy_override_postupdatepage.js and other similar tests.
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  ProfileAge: "resource://gre/modules/ProfileAge.jsm",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "updateManager",
+  "@mozilla.org/updates/update-manager;1",
+  "nsIUpdateManager"
+);
+
+const DATE_MS = 1368255600000;
+
+add_task(async function setUp() {
+  let originalTimes = (await ProfileAge())._times;
+  registerCleanupFunction(async () => {
+    removeUpdatesFile();
+    let age = await ProfileAge();
+    age._times = originalTimes;
+    await age.writeTimes();
+  });
+});
+
+// no update history, same profile created and first-use date
+add_task(async function noHistory_sameCreateAndFirstUse() {
+  removeUpdatesFile();
+  reloadUpdateManagerData();
+  Assert.equal(updateManager.updateCount, 0);
+  await updateProfileAge(DATE_MS, DATE_MS);
+  await checkExtension(DATE_MS);
+});
+
+// no update history, different profile created and first-use dates
+add_task(async function noHistory_differentCreateAndFirstUse() {
+  removeUpdatesFile();
+  reloadUpdateManagerData();
+  Assert.equal(updateManager.updateCount, 0);
+  await updateProfileAge(DATE_MS - 30000, DATE_MS);
+  await checkExtension(DATE_MS);
+});
+
+// update history
+add_task(async function history() {
+  removeUpdatesFile();
+  writeUpdatesFile(DATE_MS);
+  reloadUpdateManagerData();
+  Assert.equal(updateManager.updateCount, 1);
+  await updateProfileAge(DATE_MS - 60000, DATE_MS - 30000);
+  await checkExtension(DATE_MS);
+});
+
+async function checkExtension(expectedDate) {
+  let ext = await loadExtension(async () => {
+    let date = await browser.experiments.urlbar.lastBrowserUpdateDate();
+    browser.test.sendMessage("date", date);
+  });
+  let date = await ext.awaitMessage("date");
+  Assert.equal(date, expectedDate);
+  await ext.unload();
+}
+
+function getUpdatesFile() {
+  let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
+  let updatesFile = updateRootDir.clone();
+  updatesFile.append("updates.xml");
+  return updatesFile;
+}
+
+function removeUpdatesFile() {
+  try {
+    getUpdatesFile().remove(false);
+  } catch (e) {}
+}
+
+function reloadUpdateManagerData() {
+  updateManager
+    .QueryInterface(Ci.nsIObserver)
+    .observe(null, "um-reload-update-data", "");
+}
+
+function getUpdatesXML(installDate) {
+  return `<?xml version="1.0"?>
+  <updates xmlns="http://www.mozilla.org/2005/app-update">
+    <update appVersion="1.0" buildID="20080811053724" channel="nightly"
+            displayVersion="Version 1.0" installDate="${installDate}"
+            isCompleteUpdate="true" name="Update Test 1.0" type="minor"
+            detailsURL="http://example.com/" previousAppVersion="1.0"
+            serviceURL="https://example.com/"
+            statusText="The Update was successfully installed"
+            foregroundDownload="true">
+      <patch type="complete" URL="http://example.com/" size="775"
+             selected="true" state="succeeded"/>
+    </update>
+  </updates>`;
+}
+
+function writeUpdatesFile(installDate) {
+  const PERMS_FILE = 0o644;
+  const MODE_WRONLY = 0x02;
+  const MODE_CREATE = 0x08;
+  const MODE_TRUNCATE = 0x20;
+  let file = getUpdatesFile();
+  if (!file.exists()) {
+    file.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+  }
+  let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+    Ci.nsIFileOutputStream
+  );
+  let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
+  fos.init(file, flags, PERMS_FILE, 0);
+  let xml = getUpdatesXML(installDate);
+  fos.write(xml, xml.length);
+  fos.close();
+}
+
+async function updateProfileAge(created, firstUse) {
+  let age = await ProfileAge();
+  age._times = { created, firstUse };
+  await age.writeTimes();
+}
--- a/browser/components/urlbar/tests/ext/schema.json
+++ b/browser/components/urlbar/tests/ext/schema.json
@@ -11,12 +11,19 @@
         "parameters": []
       },
       {
         "name": "isBrowserUpdateReadyToInstall",
         "type": "function",
         "async": true,
         "description": "Returns true if there is an update ready to install.",
         "parameters": []
+      },
+      {
+        "name": "lastBrowserUpdateDate",
+        "type": "function",
+        "async": true,
+        "description": "Returns the date of the last browser update. If there's no update history, then the date the profile was first used is returned instead. The return value is milliseconds since 1 January 1970 UTC (i.e., suitable for passing to <code>new Date()</code>).",
+        "parameters": []
       }
     ]
   }
 ]