gecko-dev/netwerk/protocol/http/UserAgentUpdates.jsm
Kris Maglione e930b89c34 Bug 1514594: Part 3 - Change ChromeUtils.import API.
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8

This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:

  ChromeUtils.import("resource://gre/modules/Services.jsm");

is approximately the same as the following, in the new model:

  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs

This was done using the followng script:

https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16750

--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
2019-01-17 10:18:31 -08:00

275 lines
8.4 KiB
JavaScript

/* 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";
var EXPORTED_SYMBOLS = ["UserAgentUpdates"];
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]);
ChromeUtils.defineModuleGetter(
this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
ChromeUtils.defineModuleGetter(
this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
ChromeUtils.defineModuleGetter(
this, "OS", "resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(
this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(
this, "gUpdateTimer", "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
XPCOMUtils.defineLazyGetter(this, "gDecoder",
function() { return new TextDecoder(); }
);
XPCOMUtils.defineLazyGetter(this, "gEncoder",
function() { return new TextEncoder(); }
);
const TIMER_ID = "user-agent-updates-timer";
const PREF_UPDATES = "general.useragent.updates.";
const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
const PREF_UPDATES_URL = PREF_UPDATES + "url";
const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
const PREF_UPDATES_RETRY = PREF_UPDATES + "retry";
const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
const KEY_PREFDIR = "PrefD";
const KEY_APPDIR = "XCurProcD";
const FILE_UPDATES = "ua-update.json";
const PREF_APP_DISTRIBUTION = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
var gInitialized = false;
function readChannel(url) {
return new Promise((resolve, reject) => {
try {
let channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
channel.contentType = "application/json";
NetUtil.asyncFetch(channel, (inputStream, status) => {
if (!Components.isSuccessCode(status)) {
reject();
return;
}
let data = JSON.parse(
NetUtil.readInputStreamToString(inputStream, inputStream.available())
);
resolve(data);
});
} catch (ex) {
reject(new Error("UserAgentUpdates: Could not fetch " + url + " " +
ex + "\n" + ex.stack));
}
});
}
var UserAgentUpdates = {
init(callback) {
if (gInitialized) {
return;
}
gInitialized = true;
this._callback = callback;
this._lastUpdated = 0;
this._applySavedUpdate();
Services.prefs.addObserver(PREF_UPDATES, this);
},
uninit() {
if (!gInitialized) {
return;
}
gInitialized = false;
Services.prefs.removeObserver(PREF_UPDATES, this);
},
_applyUpdate(update) {
// Check pref again in case it has changed
if (update && this._getPref(PREF_UPDATES_ENABLED, false)) {
this._callback(update);
} else {
this._callback(null);
}
},
_applySavedUpdate() {
if (!this._getPref(PREF_UPDATES_ENABLED, false)) {
// remove previous overrides
this._applyUpdate(null);
return;
}
// try loading from profile dir, then from app dir
let dirs = [KEY_PREFDIR, KEY_APPDIR];
dirs.reduce((prevLoad, dir) => {
let file = FileUtils.getFile(dir, [FILE_UPDATES], true).path;
// tryNext returns promise to read file under dir and parse it
let tryNext = () => OS.File.read(file).then(
(bytes) => {
let update = JSON.parse(gDecoder.decode(bytes));
if (!update) {
throw new Error("invalid update");
}
return update;
}
);
// try to load next one if the previous load failed
return prevLoad ? prevLoad.catch(tryNext) : tryNext();
}, null).catch((ex) => {
if (AppConstants.platform !== "android") {
// All previous (non-Android) load attempts have failed, so we bail.
throw new Error("UserAgentUpdates: Failed to load " + FILE_UPDATES +
ex + "\n" + ex.stack);
}
// Make one last attempt to read from the Fennec APK root.
return readChannel("resource://android/" + FILE_UPDATES);
}).then((update) => {
// Apply update if loading was successful
this._applyUpdate(update);
}).catch(Cu.reportError);
this._scheduleUpdate();
},
_saveToFile(update) {
let file = FileUtils.getFile(KEY_PREFDIR, [FILE_UPDATES], true);
let path = file.path;
let bytes = gEncoder.encode(JSON.stringify(update));
OS.File.writeAtomic(path, bytes, {tmpPath: path + ".tmp"}).then(
() => {
this._lastUpdated = Date.now();
Services.prefs.setCharPref(
PREF_UPDATES_LASTUPDATED, this._lastUpdated.toString());
},
Cu.reportError
);
},
_getPref(name, def) {
try {
switch (typeof def) {
case "number": return Services.prefs.getIntPref(name);
case "boolean": return Services.prefs.getBoolPref(name);
}
return Services.prefs.getCharPref(name);
} catch (e) {
return def;
}
},
_getParameters() {
return {
"%DATE%": function() { return Date.now().toString(); },
"%PRODUCT%": function() { return Services.appinfo.name; },
"%APP_ID%": function() { return Services.appinfo.ID; },
"%APP_VERSION%": function() { return Services.appinfo.version; },
"%BUILD_ID%": function() { return Services.appinfo.appBuildID; },
"%OS%": function() { return Services.appinfo.OS; },
"%CHANNEL%": function() { return UpdateUtils.UpdateChannel; },
"%DISTRIBUTION%": function() { return this._getPref(PREF_APP_DISTRIBUTION, ""); },
"%DISTRIBUTION_VERSION%": function() { return this._getPref(PREF_APP_DISTRIBUTION_VERSION, ""); },
};
},
_getUpdateURL() {
let url = this._getPref(PREF_UPDATES_URL, "");
let params = this._getParameters();
return url.replace(/%[A-Z_]+%/g, function(match) {
let param = params[match];
// preserve the %FOO% string (e.g. as an encoding) if it's not a valid parameter
return param ? encodeURIComponent(param()) : match;
});
},
_fetchUpdate(url, success, error) {
let request = new XMLHttpRequest();
request.mozBackgroundRequest = true;
request.timeout = this._getPref(PREF_UPDATES_TIMEOUT, 60000);
request.open("GET", url, true);
request.overrideMimeType("application/json");
request.responseType = "json";
request.addEventListener("load", function() {
let response = request.response;
response ? success(response) : error();
});
request.addEventListener("error", error);
request.send();
},
_update() {
let url = this._getUpdateURL();
url && this._fetchUpdate(url,
response => { // success
// apply update and save overrides to profile
this._applyUpdate(response);
this._saveToFile(response);
this._scheduleUpdate(); // cancel any retries
},
response => { // error
this._scheduleUpdate(true /* retry */);
});
},
_scheduleUpdate(retry) {
// only schedule updates in the main process
if (Services.appinfo.processType !== Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
return;
}
let interval = this._getPref(PREF_UPDATES_INTERVAL, 604800 /* 1 week */);
if (retry) {
interval = this._getPref(PREF_UPDATES_RETRY, interval);
}
gUpdateTimer.registerTimer(TIMER_ID, this, Math.max(1, interval));
},
notify(timer) {
// timer notification
if (this._getPref(PREF_UPDATES_ENABLED, false)) {
this._update();
}
},
observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
if (data === PREF_UPDATES_ENABLED) {
this._applySavedUpdate();
} else if (data === PREF_UPDATES_INTERVAL) {
this._scheduleUpdate();
} else if (data === PREF_UPDATES_LASTUPDATED) {
// reload from file if there has been an update
let lastUpdated = parseInt(
this._getPref(PREF_UPDATES_LASTUPDATED, "0"), 0);
if (lastUpdated > this._lastUpdated) {
this._applySavedUpdate();
this._lastUpdated = lastUpdated;
}
}
break;
}
},
QueryInterface: ChromeUtils.generateQI([
Ci.nsIObserver,
Ci.nsITimerCallback,
]),
};