test_user_agent_updates.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <!DOCTYPE HTML>
  2. <html>
  3. <!--
  4. https://bugzilla.mozilla.org/show_bug.cgi?id=897221
  5. -->
  6. <head>
  7. <title>Test for User Agent Updates</title>
  8. <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  9. <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
  10. </head>
  11. <body>
  12. <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897221">Mozilla Bug 897221</a>
  13. <p id="display"></p>
  14. <div id="content" style="display: none"></div>
  15. <pre id="test">
  16. <script class="testbody" type="text/javascript">
  17. const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
  18. const PREF_UPDATES = "general.useragent.updates.";
  19. const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
  20. const PREF_UPDATES_URL = PREF_UPDATES + "url";
  21. const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
  22. const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
  23. const DEFAULT_UA = navigator.userAgent;
  24. const UA_OVERRIDE = "DummyUserAgent";
  25. const UA_ALT_OVERRIDE = "AltUserAgent";
  26. const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
  27. const UA_PARTIAL_SEP = "#";
  28. const UA_PARTIAL_TO = UA_OVERRIDE;
  29. const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
  30. const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
  31. function getUA(host) {
  32. var url = location.pathname;
  33. url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
  34. var xhr = new XMLHttpRequest();
  35. xhr.open('GET', url, false); // sync request
  36. xhr.send();
  37. is(xhr.status, 200, 'request failed');
  38. is(typeof xhr.response, 'string', 'invalid response');
  39. return xhr.response;
  40. }
  41. function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
  42. let url = location.pathname;
  43. url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
  44. let ifr = document.createElement('IFRAME');
  45. ifr.src = url;
  46. document.getElementById('content').appendChild(ifr);
  47. window.addEventListener("message", function recv(e) {
  48. ok(sameQ == (e.data.header.indexOf(expected) != -1), message);
  49. if (testNavQ) {
  50. ok(navSameQ == (e.data.nav.indexOf(expected) != -1), navMessage);
  51. }
  52. window.removeEventListener("message", recv, false);
  53. callback();
  54. }, false);
  55. }
  56. function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
  57. testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
  58. }
  59. const OVERRIDES = [
  60. {
  61. domain: 'example.org',
  62. override: '%DATE%',
  63. host: 'http://example.org'
  64. },
  65. {
  66. domain: 'test1.example.org',
  67. override: '%PRODUCT%',
  68. expected: SpecialPowers.Services.appinfo.name,
  69. host: 'http://test1.example.org'
  70. },
  71. {
  72. domain: 'test2.example.org',
  73. override: '%APP_ID%',
  74. expected: SpecialPowers.Services.appinfo.ID,
  75. host: 'http://test2.example.org'
  76. },
  77. {
  78. domain: 'sub1.test1.example.org',
  79. override: '%APP_VERSION%',
  80. expected: SpecialPowers.Services.appinfo.version,
  81. host: 'http://sub1.test1.example.org'
  82. },
  83. {
  84. domain: 'sub2.test1.example.org',
  85. override: '%BUILD_ID%',
  86. expected: SpecialPowers.Services.appinfo.appBuildID,
  87. host: 'http://sub2.test1.example.org'
  88. },
  89. {
  90. domain: 'sub1.test2.example.org',
  91. override: '%OS%',
  92. expected: SpecialPowers.Services.appinfo.OS,
  93. host: 'http://sub1.test2.example.org'
  94. },
  95. {
  96. domain: 'sub2.test2.example.org',
  97. override: UA_PARTIAL_OVERRIDE,
  98. expected: UA_PARTIAL_EXPECTED,
  99. host: 'http://sub2.test2.example.org'
  100. },
  101. ];
  102. function getServerURL() {
  103. var url = location.pathname;
  104. return location.origin + url.slice(0, url.lastIndexOf('/')) + '/user_agent_update.sjs?';
  105. }
  106. function getUpdateURL() {
  107. var url = getServerURL();
  108. var overrides = {};
  109. overrides[location.hostname] = UA_OVERRIDE;
  110. OVERRIDES.forEach(function (val) {
  111. overrides[val.domain] = val.override;
  112. });
  113. url = url + encodeURIComponent(JSON.stringify(overrides)).replace(/%25/g, '%');
  114. return url;
  115. }
  116. function testDownload(callback) {
  117. var startTime = Date.now();
  118. var url = getUpdateURL();
  119. isnot(navigator.userAgent, UA_OVERRIDE, 'UA already overridden');
  120. info('Waiting for UA update: ' + url);
  121. chromeScript.sendAsyncMessage("notify-on-update");
  122. SpecialPowers.pushPrefEnv({
  123. set: [
  124. [PREF_UPDATES_ENABLED, true],
  125. [PREF_UPDATES_URL, url],
  126. [PREF_UPDATES_TIMEOUT, 10000],
  127. [PREF_UPDATES_INTERVAL, 1] // 1 second interval
  128. ]
  129. });
  130. function waitForUpdate() {
  131. info("Update Happened");
  132. testUAIFrameNoNav(location.origin, UA_OVERRIDE, true, 'Header UA not overridden', function() {
  133. var updateTime = parseInt(getUA('http://example.org'));
  134. todo(startTime <= updateTime, 'Update was before start time');
  135. todo(updateTime <= Date.now(), 'Update was after present time');
  136. let overs = OVERRIDES;
  137. (function nextOverride() {
  138. val = overs.shift();
  139. if (val.expected) {
  140. testUAIFrameNoNav(val.host, val.expected, true, 'Incorrect URL parameter: ' + val.override, function() {
  141. overs.length ? nextOverride() : callback();
  142. });
  143. } else {
  144. nextOverride();
  145. }
  146. })();
  147. });
  148. }
  149. chromeScript.addMessageListener("useragent-update-complete", waitForUpdate);
  150. }
  151. function testBadUpdate(callback) {
  152. var url = getServerURL() + 'invalid-json';
  153. var prevOverride = navigator.userAgent;
  154. SpecialPowers.pushPrefEnv({
  155. set: [
  156. [PREF_UPDATES_URL, url],
  157. [PREF_UPDATES_INTERVAL, 1] // 1 second interval
  158. ]
  159. }, function () { setTimeout(function () {
  160. var ifr = document.createElement('IFRAME');
  161. ifr.src = "about:blank";
  162. ifr.addEventListener('load', function() {
  163. // We want to make sure a bad update doesn't cancel out previous
  164. // overrides. We do this by waiting for 5 seconds (assuming the update
  165. // occurs within 5 seconds), and check that the previous override hasn't
  166. // changed.
  167. is(navigator.userAgent, prevOverride,
  168. 'Invalid update deleted previous override');
  169. callback();
  170. }, false);
  171. document.getElementById('content').appendChild(ifr);
  172. }, 5000); });
  173. }
  174. SimpleTest.waitForExplicitFinish();
  175. SimpleTest.requestFlakyTimeout("Test sets timeouts to wait for updates to happen.");
  176. SpecialPowers.pushPrefEnv({
  177. set: [
  178. [PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
  179. ]
  180. }, function () {
  181. chromeScript.sendSyncMessage("UAO-uninit");
  182. // Sets the OVERRIDES var in the chrome script.
  183. // We do this to avoid code duplication.
  184. chromeScript.sendSyncMessage("set-overrides", OVERRIDES);
  185. // testProfileLoad, testDownload, and testProfileSave must run in this order
  186. // because testDownload depends on testProfileLoad to call UAO.init()
  187. // and testProfileSave depends on testDownload to save overrides to the profile
  188. chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
  189. });
  190. const chromeScript = SpecialPowers.loadChromeScript(_ => {
  191. // Enter update timer manager test mode
  192. Components.classes["@mozilla.org/updates/timer-manager;1"].getService(
  193. Components.interfaces.nsIObserver).observe(null, "utm-test-init", "");
  194. Components.utils.import("resource://gre/modules/UserAgentOverrides.jsm");
  195. var _notifyOnUpdate = false;
  196. var UAO = UserAgentOverrides;
  197. UAO.uninit();
  198. Components.utils.import("resource://gre/modules/FileUtils.jsm");
  199. var FU = FileUtils;
  200. const { TextDecoder, TextEncoder, OS } = Components.utils.import("resource://gre/modules/osfile.jsm");
  201. var OSF = OS.File;
  202. const KEY_PREFDIR = "PrefD";
  203. const KEY_APPDIR = "XCurProcD";
  204. const FILE_UPDATES = "ua-update.json";
  205. const UA_OVERRIDE = "DummyUserAgent";
  206. const UA_ALT_OVERRIDE = "AltUserAgent";
  207. const PREF_UPDATES = "general.useragent.updates.";
  208. const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
  209. const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
  210. Components.utils.import("resource://gre/modules/Services.jsm");
  211. Services.prefs.addObserver(PREF_UPDATES_LASTUPDATED, () => {
  212. if (_notifyOnUpdate) {
  213. _notifyOnUpdate = false; // Only notify once, for the first update.
  214. sendAsyncMessage("useragent-update-complete");
  215. }
  216. } , false);
  217. var OVERRIDES = null;
  218. function is(value, expected, message) {
  219. sendAsyncMessage("is-message", {value, expected, message});
  220. }
  221. function info(message) {
  222. sendAsyncMessage("info-message", message);
  223. }
  224. function testProfileSave(hostname) {
  225. info('Waiting for saving to profile');
  226. var file = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
  227. (function waitForSave() {
  228. OSF.exists(file).then(
  229. (exists) => {
  230. if (!exists) {
  231. setTimeout(waitForSave, 100);
  232. return;
  233. }
  234. return OSF.read(file).then(
  235. (bytes) => {
  236. info('Saved new overrides');
  237. var decoder = new TextDecoder();
  238. var overrides = JSON.parse(decoder.decode(bytes));
  239. is(overrides[hostname], UA_OVERRIDE, 'Incorrect saved override');
  240. OVERRIDES.forEach(function (val) {
  241. val.expected && is(overrides[val.domain], val.expected,
  242. 'Incorrect saved override: ' + val.override);
  243. });
  244. sendAsyncMessage("testProfileSaveDone");
  245. }
  246. );
  247. }
  248. ).then(null,
  249. (reason) => {
  250. throw reason
  251. }
  252. );
  253. })();
  254. }
  255. function testProfileLoad(hostname) {
  256. var file = FU.getFile(KEY_APPDIR, [FILE_UPDATES]).path;
  257. var encoder = new TextEncoder();
  258. var overrides = {};
  259. overrides[hostname] = UA_ALT_OVERRIDE;
  260. var bytes = encoder.encode(JSON.stringify(overrides));
  261. var badfile = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
  262. var badbytes = encoder.encode("null");
  263. OSF.writeAtomic(file, bytes, {tmpPath: file + ".tmp"}).then(
  264. () => OSF.writeAtomic(badfile, badbytes, {tmpPath: badfile + ".tmp"})
  265. ).then(
  266. () => {
  267. sendAsyncMessage("testProfileLoadDone");
  268. },
  269. (reason) => {
  270. throw reason
  271. }
  272. );
  273. }
  274. addMessageListener("testProfileSave", testProfileSave);
  275. addMessageListener("testProfileLoad", testProfileLoad);
  276. addMessageListener("set-overrides", function(overrides) { OVERRIDES = overrides});
  277. addMessageListener("UAO-init", function() { UAO.init(); });
  278. addMessageListener("UAO-uninit", function() { UAO.uninit(); });
  279. addMessageListener("notify-on-update", () => { _notifyOnUpdate = true });
  280. });
  281. chromeScript.addMessageListener("testProfileSaveDone", SimpleTest.finish);
  282. chromeScript.addMessageListener("testProfileLoadDone", function() {
  283. SpecialPowers.pushPrefEnv({
  284. set: [[PREF_UPDATES_ENABLED, true]]
  285. }, function () {
  286. // initialize UserAgentOverrides.jsm and
  287. // UserAgentUpdates.jsm and load saved file
  288. chromeScript.sendSyncMessage("UAO-init");
  289. (function waitForLoad() {
  290. var ifr = document.createElement('IFRAME');
  291. ifr.src = "about:blank";
  292. ifr.addEventListener('load', function() {
  293. var nav = ifr.contentWindow.navigator;
  294. if (nav.userAgent !== UA_ALT_OVERRIDE) {
  295. setTimeout(waitForLoad, 100);
  296. return;
  297. }
  298. testUAIFrameNoNav(location.origin, UA_ALT_OVERRIDE, true, 'Did not apply saved override', function () {
  299. testDownload(function() {
  300. testBadUpdate(function() {
  301. chromeScript.sendAsyncMessage("testProfileSave", location.hostname);
  302. })
  303. })
  304. });
  305. }, true);
  306. document.getElementById('content').appendChild(ifr);
  307. })();
  308. });
  309. });
  310. chromeScript.addMessageListener("is-message", function(params) {
  311. let {value, expected, message} = params;
  312. is(value, expected, message);
  313. });
  314. chromeScript.addMessageListener("info-message", function(message) {
  315. info(message);
  316. });
  317. </script>
  318. </pre>
  319. </body>
  320. </html>