Backed out 4 changesets (bug 1513574) for causing several browser chrome failures. CLOSED TREE

Backed out changeset 7d950fc452fb (bug 1513574)
Backed out changeset 11f015a3e739 (bug 1513574)
Backed out changeset be6959a563f6 (bug 1513574)
Backed out changeset ce51efd054b8 (bug 1513574)

--HG--
extra : histedit_source : a7bc0055925ac352826572f96730f3d90815bda0
This commit is contained in:
Cosmin Sabou 2019-09-16 12:40:40 +03:00
Родитель c9c01ce286
Коммит b1cfe7b829
31 изменённых файлов: 1520 добавлений и 95 удалений

Просмотреть файл

@ -13,7 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1339722
/**
* Test for Bug 1339722
* 1. Wait for "http-on-modify-request" for the iframe load.
* 1. Wait for "http-on-useragent-request" for the iframe load.
* 2. In the observer, access it's window proxy to trigger DOMWindowCreated.
* 3. In the event handler, delete the iframe so that the frameloader would be
* destoryed in the middle of ReallyStartLoading.
@ -22,10 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1339722
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
// This topic used to be http-on-useragent-request, but that got removed in
// bug 1513574. on-modify-request is called around the same time, and should
// behave similarly.
const TOPIC = "http-on-modify-request";
const TOPIC = "http-on-useragent-request";
let win;
Services.obs.addObserver({
observe(subject, topic, data) {

Просмотреть файл

@ -693,6 +693,12 @@ pref("dom.phonenumber.substringmatching.VE", 7);
pref("gfx.canvas.azure.backends", "skia");
// See ua-update.json.in for the packaged UA override list
pref("general.useragent.updates.enabled", true);
pref("general.useragent.updates.url", "https://dynamicua.cdn.mozilla.net/0/%APP_ID%");
pref("general.useragent.updates.interval", 604800); // 1 week
pref("general.useragent.updates.retry", 86400); // 1 day
// When true, phone number linkification is enabled.
pref("browser.ui.linkify.phone", false);

Просмотреть файл

@ -16,6 +16,9 @@ with Files('lint*'):
with Files('mobile*'):
BUG_COMPONENT = ('Firefox for Android', 'General')
with Files('ua-update.json.in'):
BUG_COMPONENT = ('Firefox for Android', 'General')
with Files('omnijar/**'):
BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
@ -61,6 +64,10 @@ else:
'!/dist/fat-aar/output/defaults/pref/{arch}/geckoview-prefs.js'.format(arch=arch),
]
FINAL_TARGET_PP_FILES += [
'ua-update.json.in',
]
if CONFIG['MOZ_ANDROID_GOOGLE_VR']:
FINAL_TARGET_FILES += [
'/' + CONFIG['MOZ_ANDROID_GOOGLE_VR_LIBS'] + 'libgvr.so',

Просмотреть файл

@ -0,0 +1,9 @@
#filter slashslash
// Everything after the first // on a line will be removed by the preproccesor.
// Send these sites a custom user-agent. Bugs should be included with an entry.
// Format:
// "example.org": "regex_string#new_string"
// "#" hash sign is the separator. If no hash sign, the full string is used as replacement.
// NOTE: trailing commas are not valid JSON and will prevent the CDN from syncing.
{
}

Просмотреть файл

@ -68,6 +68,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/Downloads.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"UserAgentOverrides",
"resource://gre/modules/UserAgentOverrides.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(
@ -119,19 +125,6 @@ XPCOMUtils.defineLazyServiceGetter(
"nsIUUIDGenerator"
);
XPCOMUtils.defineLazyGetter(this, "DEFAULT_UA", function() {
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
).userAgent;
});
XPCOMUtils.defineLazyGetter(this, "DESKTOP_UA", function() {
return DEFAULT_UA.replace(
/Android \d.+?; [a-zA-Z]+/,
"X11; Linux x86_64"
).replace(/Gecko\/[0-9\.]+/, "Gecko/20100101");
});
if (AppConstants.MOZ_ENABLE_PROFILER_SPS) {
XPCOMUtils.defineLazyServiceGetter(
this,
@ -602,7 +595,6 @@ var BrowserApp = {
GlobalEventDispatcher.registerListener(this, [
"Browser:LoadManifest",
"Browser:Quit",
"DesktopMode:Change",
"Fonts:Reload",
"FormHistory:Init",
"FullScreen:Exit",
@ -660,6 +652,7 @@ var BrowserApp = {
CharacterEncoding.init();
ActivityObserver.init();
RemoteDebugger.init(window);
DesktopUserAgent.init();
Distribution.init();
Tabs.init();
SearchEngines.init();
@ -2333,13 +2326,6 @@ var BrowserApp = {
FontEnumerator.updateFontList();
break;
case "DesktopMode:Change":
let tab = this.getTabForId(data.tabId);
if (tab) {
tab.reloadWithMode(data.desktopMode);
}
break;
case "FormHistory:Init": {
// Force creation/upgrade of formhistory.sqlite
FormHistory.count(
@ -4017,6 +4003,94 @@ var LightWeightThemeStuff = {
},
};
var DesktopUserAgent = {
DESKTOP_UA: null,
TCO_DOMAIN: "t.co",
TCO_REPLACE: / Gecko.*/,
init: function ua_init() {
GlobalEventDispatcher.registerListener(this, "DesktopMode:Change");
UserAgentOverrides.addComplexOverride(this.onRequest.bind(this));
// See https://developer.mozilla.org/en/Gecko_user_agent_string_reference
this.DESKTOP_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler)
.userAgent.replace(/Android \d.+?; [a-zA-Z]+/, "X11; Linux x86_64")
.replace(/Gecko\/[0-9\.]+/, "Gecko/20100101");
},
onRequest: function(channel, defaultUA) {
if (AppConstants.NIGHTLY_BUILD && this.TCO_DOMAIN == channel.URI.host) {
// Force the referrer
channel.referrer = channel.URI;
// Send a bot-like UA to t.co to get a real redirect. We strip off the
// "Gecko/x.y Firefox/x.y" part
return defaultUA.replace(this.TCO_REPLACE, "");
}
let channelWindow = this._getWindowForRequest(channel);
let tab = BrowserApp.getTabForWindow(channelWindow);
if (tab) {
return this.getUserAgentForTab(tab);
}
return null;
},
getUserAgentForTab: function ua_getUserAgentForTab(aTab) {
// Send desktop UA if "Request Desktop Site" is enabled.
if (aTab.desktopMode) {
return this.DESKTOP_UA;
}
return null;
},
_getRequestLoadContext: function ua_getRequestLoadContext(aRequest) {
if (aRequest && aRequest.notificationCallbacks) {
try {
return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) {}
}
if (
aRequest &&
aRequest.loadGroup &&
aRequest.loadGroup.notificationCallbacks
) {
try {
return aRequest.loadGroup.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
} catch (ex) {}
}
return null;
},
_getWindowForRequest: function ua_getWindowForRequest(aRequest) {
let loadContext = this._getRequestLoadContext(aRequest);
if (loadContext) {
try {
return loadContext.associatedWindow;
} catch (e) {
// loadContext.associatedWindow can throw when there's no window
}
}
return null;
},
onEvent: function ua_onEvent(event, data, callback) {
if (event === "DesktopMode:Change") {
let tab = BrowserApp.getTabForId(data.tabId);
if (tab) {
tab.reloadWithMode(data.desktopMode);
}
}
},
};
function nsBrowserAccess() {}
nsBrowserAccess.prototype = {
@ -4318,13 +4392,6 @@ Tab.prototype = {
this.browser.docShell.setOriginAttributes(attrs);
let desktopMode = "desktopMode" in aParams ? aParams.desktopMode : false;
if (desktopMode) {
this.browser.docShell.customUserAgent = DESKTOP_UA;
} else {
this.browser.docShell.customUserAgent = "";
}
// Set the new docShell load flags based on network state.
if (Tabs.useCache) {
this.browser.docShell.defaultLoadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
@ -4544,14 +4611,6 @@ Tab.prototype = {
// We were redirected; reload the original URL
url = this.originalURI.spec;
}
if (aDesktopMode) {
this.browser.docShell.customUserAgent = DESKTOP_UA;
} else {
// Clear custom UA
this.browser.docShell.customUserAgent = "";
}
let loadURIOptions = {
triggeringPrincipal: this.browser.contentPrincipal,
loadFlags,

Просмотреть файл

@ -177,6 +177,7 @@
@BINPATH@/@PREF_DIR@/mobile.js
#endif # MOZ_GECKOVIEW_JAR
@BINPATH@/@PREF_DIR@/channel-prefs.js
@BINPATH@/ua-update.json
@BINPATH@/defaults/autoconfig/prefcalls.js
; [Layout Engine Resources]

Просмотреть файл

@ -24,6 +24,21 @@ Migrated from Robocop: https://bugzilla.mozilla.org/show_bug.cgi?id=1184186
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = chromeWin.BrowserApp;
// Add a new 'desktop mode' tab with our test page
let desktopTab = BrowserApp.addTab(TestURI.spec, { selected: true, parentId: BrowserApp.selectedTab.id, desktopMode: true });
let desktopBrowser = desktopTab.browser;
await promiseBrowserEvent(desktopBrowser, "load");
// Some debugging
info("desktop: " + desktopBrowser.contentWindow.navigator.userAgent);
info("desktop: " + desktopBrowser.contentDocument.body.innerHTML);
// Check the server UA and the navigator UA for 'desktop'
ok(desktopBrowser.contentWindow.navigator.userAgent.includes("Linux x86_64"), "window.navigator.userAgent has 'Linux' in it");
ok(desktopBrowser.contentDocument.body.innerHTML.includes("Linux x86_64"), "HTTP header 'User-Agent' has 'Linux' in it");
BrowserApp.closeTab(desktopTab);
// Add a new 'mobile mode' tab with our test page
let mobileTab = BrowserApp.addTab(TestURI.spec, { selected: true, parentId: BrowserApp.selectedTab.id });
let mobileBrowser = mobileTab.browser;
@ -38,58 +53,7 @@ Migrated from Robocop: https://bugzilla.mozilla.org/show_bug.cgi?id=1184186
ok(mobileBrowser.contentWindow.navigator.userAgent.includes("Android"), "window.navigator.userAgent has 'Android' in it");
ok(mobileBrowser.contentDocument.body.innerHTML.includes("Android"), "HTTP header 'User-Agent' has 'Android' in it");
mobileTab.reloadWithMode(true);
await promiseBrowserEvent(mobileBrowser, "load");
// Some debugging
info("desktop: " + mobileBrowser.contentWindow.navigator.userAgent);
info("desktop: " + mobileBrowser.contentDocument.body.innerHTML);
// Check the server UA and the navigator UA for 'desktop'
ok(mobileBrowser.contentWindow.navigator.userAgent.includes("Linux x86_64"), "window.navigator.userAgent has 'Linux' in it");
ok(mobileBrowser.contentDocument.body.innerHTML.includes("Linux x86_64"), "HTTP header 'User-Agent' has 'Linux' in it");
BrowserApp.closeTab(mobileTab);
// Add a new 'desktop mode' tab with our test page
let desktopTab = BrowserApp.addTab(TestURI.spec, { selected: true, parentId: BrowserApp.selectedTab.id, desktopMode: true });
let desktopBrowser = desktopTab.browser;
await promiseBrowserEvent(desktopBrowser, "load");
// Some debugging
info("desktop: " + desktopBrowser.contentWindow.navigator.userAgent);
info("desktop: " + desktopBrowser.contentDocument.body.innerHTML);
// Check the server UA and the navigator UA for 'desktop'
ok(desktopBrowser.contentWindow.navigator.userAgent.includes("Linux x86_64"), "window.navigator.userAgent has 'Linux' in it");
ok(desktopBrowser.contentDocument.body.innerHTML.includes("Linux x86_64"), "HTTP header 'User-Agent' has 'Linux' in it");
// should reload, and keep desktop mode
desktopTab.reloadWithMode(true);
await promiseBrowserEvent(desktopBrowser, "load");
// Some debugging
info("desktop: " + desktopBrowser.contentWindow.navigator.userAgent);
info("desktop: " + desktopBrowser.contentDocument.body.innerHTML);
// Check the server UA and the navigator UA for 'desktop'
ok(desktopBrowser.contentWindow.navigator.userAgent.includes("Linux x86_64"), "window.navigator.userAgent has 'Linux' in it");
ok(desktopBrowser.contentDocument.body.innerHTML.includes("Linux x86_64"), "HTTP header 'User-Agent' has 'Linux' in it");
// Now reload in mobile mode
desktopTab.reloadWithMode(false);
await promiseBrowserEvent(desktopBrowser, "load");
// Some debugging
info("mobile: " + desktopBrowser.contentWindow.navigator.userAgent);
info("mobile: " + desktopBrowser.contentDocument.body.innerHTML);
// Check the server UA and the navigator UA for 'mobile'
// We only check for 'Android' because we don't know the version or if it's phone or tablet
ok(desktopBrowser.contentWindow.navigator.userAgent.includes("Android"), "window.navigator.userAgent has 'Android' in it");
ok(desktopBrowser.contentDocument.body.innerHTML.includes("Android"), "HTTP header 'User-Agent' has 'Android' in it");
BrowserApp.closeTab(desktopTab);
});
</script>

Просмотреть файл

@ -205,6 +205,10 @@ pref("security.remote_settings.crlite_filters.signer", "onecrl.content-signature
pref("general.useragent.compatMode.firefox", false);
// This pref exists only for testing purposes. In order to disable all
// overrides by default, don't initialize UserAgentOverrides.jsm.
pref("general.useragent.site_specific_overrides", true);
pref("general.config.obscure_value", 13); // for MCD .cfg files
pref("general.warnOnAboutConfig", true);

Просмотреть файл

@ -60,6 +60,7 @@ class RequestContext final : public nsIRequestContext, public nsITimerCallback {
uint64_t mID;
Atomic<uint32_t> mBlockingTransactionCount;
nsAutoPtr<SpdyPushCache> mSpdyCache;
nsCString mUserAgentOverride;
typedef nsCOMPtr<nsIRequestTailUnblockCallback> PendingTailRequest;
// Number of known opened non-tailed requets
@ -186,6 +187,15 @@ void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
uint64_t RequestContext::GetID() { return mID; }
const nsACString& RequestContext::GetUserAgentOverride() {
return mUserAgentOverride;
}
void RequestContext::SetUserAgentOverride(
const nsACString& aUserAgentOverride) {
mUserAgentOverride = aUserAgentOverride;
}
NS_IMETHODIMP
RequestContext::AddNonTailRequest() {
MOZ_ASSERT(NS_IsMainThread());

Просмотреть файл

@ -95,4 +95,10 @@ interface nsILoadGroup : nsIRequest
* the docShell has created the default request.)
*/
attribute nsLoadFlags defaultLoadFlags;
/**
* The cached user agent override created by UserAgentOverrides.jsm. Used
* for all sub-resource requests in the loadgroup.
*/
attribute ACString userAgentOverrideCache;
};

Просмотреть файл

@ -93,6 +93,11 @@ interface nsIRequestContext : nsISupports
*/
[notxpcom,nostdcall] attribute SpdyPushCachePtr spdyPushCache;
/**
* This holds a cached value of the user agent override.
*/
[notxpcom,nostdcall] attribute ACString userAgentOverride;
/**
* Increases/decrease the number of non-tailed requests in this context.
* If the count drops to zero, all tail-blocked callbacks are notified

Просмотреть файл

@ -715,6 +715,19 @@ nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) {
return NS_OK;
}
NS_IMETHODIMP
nsLoadGroup::GetUserAgentOverrideCache(nsACString& aUserAgentOverrideCache) {
aUserAgentOverrideCache = mUserAgentOverrideCache;
return NS_OK;
}
NS_IMETHODIMP
nsLoadGroup::SetUserAgentOverrideCache(
const nsACString& aUserAgentOverrideCache) {
mUserAgentOverrideCache = aUserAgentOverrideCache;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
void nsLoadGroup::TelemetryReport() {

Просмотреть файл

@ -87,6 +87,8 @@ class nsLoadGroup : public nsILoadGroup,
mozilla::TimeStamp mDefaultRequestCreationTime;
uint32_t mTimedRequests;
uint32_t mCachedRequests;
nsCString mUserAgentOverrideCache;
};
} // namespace net

Просмотреть файл

@ -485,7 +485,8 @@ HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
NS_IMETHODIMP
HttpBaseChannel::SetDocshellUserAgentOverride() {
// This sets the docshell specific user agent override
// This sets the docshell specific user agent override, it will be overwritten
// by UserAgentOverrides.jsm if site-specific user agent overrides are set.
nsresult rv;
nsCOMPtr<nsILoadContext> loadContext;
NS_QueryNotificationCallbacks(this, loadContext);

Просмотреть файл

@ -0,0 +1,33 @@
/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { UserAgentOverrides } = ChromeUtils.import(
"resource://gre/modules/UserAgentOverrides.jsm"
);
function UAOverridesBootstrapper() {
this.init();
}
UAOverridesBootstrapper.prototype = {
init: function uaob_init() {
Services.obs.addObserver(this, "profile-change-net-teardown");
UserAgentOverrides.init();
},
observe: function uaob_observe(aSubject, aTopic, aData) {
if (aTopic == "profile-change-net-teardown") {
Services.obs.removeObserver(this, "profile-change-net-teardown");
UserAgentOverrides.uninit();
}
},
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
classID: Components.ID("{965b0ca8-155b-11e7-93ae-92361f002671}"),
};
var EXPORTED_SYMBOLS = ["UAOverridesBootstrapper"];

Просмотреть файл

@ -0,0 +1,188 @@
/* 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 = ["UserAgentOverrides"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { UserAgentUpdates } = ChromeUtils.import(
"resource://gre/modules/UserAgentUpdates.jsm"
);
const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250;
// lazy load nsHttpHandler to improve startup performance.
XPCOMUtils.defineLazyGetter(this, "DEFAULT_UA", function() {
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
).userAgent;
});
var gPrefBranch;
var gOverrides = new Map();
var gUpdatedOverrides;
var gOverrideForHostCache = new Map();
var gInitialized = false;
var gOverrideFunctions = [
function(aHttpChannel) {
return UserAgentOverrides.getOverrideForURI(aHttpChannel.URI);
},
];
var gBuiltUAs = new Map();
var UserAgentOverrides = {
init: function uao_init() {
if (gInitialized) {
return;
}
gPrefBranch = Services.prefs.getBranch("general.useragent.override.");
gPrefBranch.addObserver("", buildOverrides);
Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides);
try {
Services.obs.addObserver(
HTTP_on_useragent_request,
"http-on-useragent-request"
);
} catch (x) {
// The http-on-useragent-request notification is disallowed in content processes.
}
try {
UserAgentUpdates.init(function(overrides) {
gOverrideForHostCache.clear();
if (overrides) {
for (let domain in overrides) {
overrides[domain] = getUserAgentFromOverride(overrides[domain]);
}
overrides.get = function(key) {
return this[key];
};
}
gUpdatedOverrides = overrides;
});
buildOverrides();
} catch (e) {
// UserAgentOverrides is initialized before profile is ready.
// UA override might not work correctly.
}
Services.obs.notifyObservers(null, "useragentoverrides-initialized");
gInitialized = true;
},
addComplexOverride: function uao_addComplexOverride(callback) {
// Add to front of array so complex overrides have precedence
gOverrideFunctions.unshift(callback);
},
getOverrideForURI: function uao_getOverrideForURI(aURI) {
let host = aURI.asciiHost;
if (!gInitialized || (!gOverrides.size && !gUpdatedOverrides) || !host) {
return null;
}
let override = gOverrideForHostCache.get(host);
if (override !== undefined) {
return override;
}
function findOverride(overrides) {
let searchHost = host;
let userAgent = overrides.get(searchHost);
while (!userAgent) {
let dot = searchHost.indexOf(".");
if (dot === -1) {
return null;
}
searchHost = searchHost.slice(dot + 1);
userAgent = overrides.get(searchHost);
}
return userAgent;
}
override =
(gOverrides.size && findOverride(gOverrides)) ||
(gUpdatedOverrides && findOverride(gUpdatedOverrides));
if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) {
gOverrideForHostCache.clear();
}
gOverrideForHostCache.set(host, override);
return override;
},
uninit: function uao_uninit() {
if (!gInitialized) {
return;
}
gInitialized = false;
gPrefBranch.removeObserver("", buildOverrides);
Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides);
Services.obs.removeObserver(
HTTP_on_useragent_request,
"http-on-useragent-request"
);
},
};
function getUserAgentFromOverride(override) {
let userAgent = gBuiltUAs.get(override);
if (userAgent !== undefined) {
return userAgent;
}
let [search, replace] = override.split("#", 2);
if (search && replace) {
userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace);
} else {
userAgent = override;
}
gBuiltUAs.set(override, userAgent);
return userAgent;
}
function buildOverrides() {
gOverrides.clear();
gOverrideForHostCache.clear();
if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED)) {
return;
}
let domains = gPrefBranch.getChildList("");
for (let domain of domains) {
let override = gPrefBranch.getCharPref(domain);
let userAgent = getUserAgentFromOverride(override);
if (userAgent != DEFAULT_UA) {
gOverrides.set(domain, userAgent);
}
}
}
function HTTP_on_useragent_request(aSubject, aTopic, aData) {
let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
for (let callback of gOverrideFunctions) {
let modifiedUA = callback(channel, DEFAULT_UA);
if (modifiedUA) {
channel.setRequestHeader("User-Agent", modifiedUA, false);
return;
}
}
}

Просмотреть файл

@ -0,0 +1,333 @@
/* 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]),
};

Просмотреть файл

@ -5,6 +5,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [
{
'cid': '{965b0ca8-155b-11e7-93ae-92361f002671}',
'contract_ids': ['@mozilla.org/network/ua-overrides-bootstrapper;1'],
'jsm': 'resource://gre/modules/UAOverridesBootstrapper.jsm',
'constructor': 'UAOverridesBootstrapper',
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
},
{
'cid': '{b4f96c89-5238-450c-8bda-e12c26f1d150}',
'contract_ids': ['@mozilla.org/network/well-known-opportunistic-utils;1'],

Просмотреть файл

@ -123,6 +123,11 @@ IPDL_SOURCES += [
'PHttpChannel.ipdl',
]
EXTRA_JS_MODULES += [
'UserAgentOverrides.jsm',
'UserAgentUpdates.jsm',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
@ -136,6 +141,7 @@ LOCAL_INCLUDES += [
]
EXTRA_JS_MODULES += [
'UAOverridesBootstrapper.jsm',
'WellKnownOpportunisticUtils.jsm',
]

Просмотреть файл

@ -444,6 +444,9 @@ nsresult nsHttpChannel::PrepareToConnect() {
// notify "http-on-modify-request" observers
CallOnModifyRequestObservers();
SetLoadGroupUserAgentOverride();
// Check if request was cancelled during on-modify-request or on-useragent.
if (mCanceled) {
return mStatus;
}
@ -509,7 +512,8 @@ void nsHttpChannel::HandleOnBeforeConnect() {
nsresult nsHttpChannel::OnBeforeConnect() {
nsresult rv;
// Check if request was cancelled during suspend AFTER on-modify-request
// Check if request was cancelled during suspend AFTER on-modify-request or
// on-useragent.
if (mCanceled) {
return mStatus;
}
@ -6193,7 +6197,9 @@ nsHttpChannel::CancelByURLClassifier(nsresult aErrorCode) {
// notify "http-on-modify-request" observers
CallOnModifyRequestObservers();
// Check if request was cancelled during on-modify-request
SetLoadGroupUserAgentOverride();
// Check if request was cancelled during on-modify-request or on-useragent.
if (mCanceled) {
return mStatus;
}
@ -9638,6 +9644,48 @@ void nsHttpChannel::MaybeWarnAboutAppCache() {
}
}
void nsHttpChannel::SetLoadGroupUserAgentOverride() {
nsCOMPtr<nsIURI> uri;
GetURI(getter_AddRefs(uri));
nsAutoCString uriScheme;
if (uri) {
uri->GetScheme(uriScheme);
}
// We don't need a UA for file: protocols.
if (uriScheme.EqualsLiteral("file")) {
gHttpHandler->OnUserAgentRequest(this);
return;
}
nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
nsCOMPtr<nsIRequestContext> rc;
if (rcsvc) {
rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(rc));
}
nsAutoCString ua;
if (nsContentUtils::IsNonSubresourceRequest(this)) {
gHttpHandler->OnUserAgentRequest(this);
if (rc) {
GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
rc->SetUserAgentOverride(ua);
}
} else {
GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
// Don't overwrite the UA if it is already set (eg by an XHR with explicit
// UA).
if (ua.IsEmpty()) {
if (rc) {
SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"),
rc->GetUserAgentOverride(), false);
} else {
gHttpHandler->OnUserAgentRequest(this);
}
}
}
}
// Step 10 of HTTP-network-or-cache fetch
void nsHttpChannel::SetOriginHeader() {
if (mRequestHead.IsGet() || mRequestHead.IsHead()) {

Просмотреть файл

@ -533,6 +533,8 @@ class nsHttpChannel final : public HttpBaseChannel,
void MaybeWarnAboutAppCache();
void SetLoadGroupUserAgentOverride();
void SetOriginHeader();
void SetDoNotTrack();

Просмотреть файл

@ -383,6 +383,24 @@ void nsHttpHandler::SetFastOpenOSSupport() {
mFastOpenSupported ? "" : "not"));
}
void nsHttpHandler::EnsureUAOverridesInit() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
static bool initDone = false;
if (initDone) {
return;
}
nsresult rv;
nsCOMPtr<nsISupports> bootstrapper =
do_GetService("@mozilla.org/network/ua-overrides-bootstrapper;1", &rv);
MOZ_ASSERT(bootstrapper);
MOZ_ASSERT(NS_SUCCEEDED(rv));
initDone = true;
}
nsHttpHandler::~nsHttpHandler() {
LOG(("Deleting nsHttpHandler [this=%p]\n", this));
@ -2046,6 +2064,11 @@ nsHttpHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* givenProxyInfo,
net_EnsurePSMInit();
}
if (XRE_IsParentProcess()) {
// Load UserAgentOverrides.jsm before any HTTP request is issued.
EnsureUAOverridesInit();
}
uint64_t channelId;
nsresult rv = NewChannelId(channelId);
NS_ENSURE_SUCCESS(rv, rv);

Просмотреть файл

@ -375,6 +375,11 @@ class nsHttpHandler final : public nsIHttpProtocolHandler,
NotifyObservers(chan, NS_HTTP_ON_STOP_REQUEST_TOPIC);
}
// Called by the channel and cached in the loadGroup
void OnUserAgentRequest(nsIHttpChannel* chan) {
NotifyObservers(chan, NS_HTTP_ON_USERAGENT_REQUEST_TOPIC);
}
// Called by the channel before setting up the transaction
void OnBeforeConnect(nsIHttpChannel* chan) {
NotifyObservers(chan, NS_HTTP_ON_BEFORE_CONNECT_TOPIC);
@ -482,6 +487,8 @@ class nsHttpHandler final : public nsIHttpProtocolHandler,
void SetFastOpenOSSupport();
void EnsureUAOverridesInit();
// Checks if there are any user certs or active smart cards on a different
// thread. Updates mSpeculativeConnectEnabled when done.
void MaybeEnableSpeculativeConnect();

Просмотреть файл

@ -181,6 +181,15 @@ interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
*/
#define NS_HTTP_ON_MAY_CHANGE_PROCESS_TOPIC "http-on-may-change-process"
/**
* Before an HTTP request corresponding to a channel with the LOAD_DOCUMENT_URI
* flag is sent to the server, this observer topic is notified. The observer of
* this topic can then choose to modify the user agent for this request before
* the request is actually sent to the server. Additionally, the modified user
* agent will be propagated to sub-resource requests from the same load group.
*/
#define NS_HTTP_ON_USERAGENT_REQUEST_TOPIC "http-on-useragent-request"
/**
* This topic is notified for every http channel right after it called
* OnStopRequest on its listener, regardless whether it was finished

Просмотреть файл

@ -3,6 +3,8 @@ support-files =
method.sjs
partial_content.sjs
rel_preconnect.sjs
user_agent.sjs
user_agent_update.sjs
set_cookie_xhr.sjs
reset_cookie_xhr.sjs
web_packaged_app.sjs
@ -65,7 +67,10 @@ support-files =
[test_rel_preconnect.html]
[test_redirect_ref.html]
[test_uri_scheme.html]
[test_user_agent_overrides.html]
[test_user_agent_updates.html]
skip-if = (verify && debug && os == 'mac')
[test_user_agent_updates_reset.html]
[test_viewsource_unlinkable.html]
[test_xhr_method_case.html]
[test_1331680.html]

Просмотреть файл

@ -0,0 +1,247 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=782453
-->
<head>
<title>Test for User Agent Overrides</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782453">Mozilla Bug 782453</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<script class="testbody" type="text/javascript">
const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
const PREF_OVERRIDES_BRANCH = "general.useragent.override.";
const DEFAULT_UA = navigator.userAgent;
const UA_WHOLE_OVERRIDE = "DummyUserAgent";
const UA_WHOLE_EXPECTED = UA_WHOLE_OVERRIDE;
const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
const UA_PARTIAL_SEP = "#";
const UA_PARTIAL_TO = UA_WHOLE_OVERRIDE;
const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
const UA_PARTIAL_FROM2 = "[0-9]+";
const UA_PARTIAL_TO2 = "number";
const UA_PARTIAL_OVERRIDE2 = UA_PARTIAL_FROM2 + UA_PARTIAL_SEP + UA_PARTIAL_TO2;
const UA_PARTIAL_EXPECTED2 = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM2, 'g'), UA_PARTIAL_TO2);
function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
let url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
let ifr = document.createElement('IFRAME');
ifr.src = url;
document.getElementById('content').appendChild(ifr);
window.addEventListener("message", function recv(e) {
ok(sameQ == (e.data.header.includes(expected)), message);
if (testNavQ) {
ok(navSameQ == (e.data.nav.includes(expected)), navMessage);
}
window.removeEventListener("message", recv);
callback();
});
}
function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
}
function testUA(options, callback) {
var [domain, override, test_hosts, expected] =
[options.domain, options.override, options.test_hosts, options.expected];
(function nextTest() {
let test_host = test_hosts.shift();
info("Overriding " + domain + " with " + override + " for " + test_host);
function is_subdomain(host) {
var [test_domain] = host.slice(host.lastIndexOf('/') + 1).split(':', 1);
return test_domain === domain || test_domain.endsWith('.' + domain);
}
var localhost = location.origin;
var overrideNavigator = is_subdomain(localhost);
var navigator_ua, test_ua;
if (overrideNavigator) {
navigator_ua = navigator.userAgent;
}
let url = location.pathname;
url = test_host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
let ifr = document.createElement('IFRAME');
ifr.src = url;
document.getElementById('content').appendChild(ifr);
window.addEventListener("message", function recv(e) {
test_ua = e.data.header;
SpecialPowers.pushPrefEnv({
set: [[PREF_OVERRIDES_BRANCH + domain, override]],
}, function () {
testUAIFrame(test_host, expected, true, 'Header UA not overridden at step ' + (++step), true,
true, 'Navigator UA not overridden at step ' + (++step), function () {
// clear the override pref to undo overriding the UA
SpecialPowers.pushPrefEnv({
clear: [[PREF_OVERRIDES_BRANCH + domain]],
}, function () {
testUAIFrameNoNav(test_host, test_ua, true, 'Header UA not restored at step ' + (++step), function() {
test_hosts.length ? nextTest() : callback();
});
});
});
});
window.removeEventListener("message", recv);
});
})();
}
var step = 0; // for logging
var tests = [
// should override both header and navigator.userAgent
{
domain: location.hostname,
override: UA_WHOLE_OVERRIDE,
test_hosts: [location.origin],
expected: UA_WHOLE_EXPECTED
},
// should support partial overrides
{
domain: location.hostname,
override: UA_PARTIAL_OVERRIDE,
test_hosts: [location.origin],
expected: UA_PARTIAL_EXPECTED
},
{
domain: location.hostname,
override: UA_PARTIAL_OVERRIDE2,
test_hosts: [location.origin],
expected: UA_PARTIAL_EXPECTED2
},
// should match domain and subdomains
{
domain: 'example.org',
override: UA_WHOLE_OVERRIDE,
test_hosts: ['http://example.org',
'http://test1.example.org',
'http://sub1.test1.example.org'],
expected: UA_WHOLE_EXPECTED
},
// should not match superdomains
{
domain: 'sub1.test1.example.org',
override: UA_WHOLE_OVERRIDE,
test_hosts: ['http://example.org',
'http://test1.example.org'],
expected: DEFAULT_UA
},
// should work with https
{
domain: 'example.com',
override: UA_WHOLE_OVERRIDE,
test_hosts: ['https://example.com',
'https://test1.example.com',
'https://sub1.test1.example.com'],
expected: UA_WHOLE_EXPECTED
},
];
// test that UA is not overridden when the 'site_specific_overrides' pref is off
function testInactive(callback) {
SpecialPowers.pushPrefEnv({
set: [
[PREF_OVERRIDES_ENABLED, false],
[PREF_OVERRIDES_BRANCH + location.hostname, UA_WHOLE_OVERRIDE]
]
}, function () {
testUAIFrame(location.origin, UA_WHOLE_OVERRIDE, false, 'Failed to disabled header UA override at step ' + (++step),
true, false, 'Failed to disable navigator UA override at step + ' + (++step), function () {
SpecialPowers.pushPrefEnv({
clear: [
[PREF_OVERRIDES_ENABLED],
[PREF_OVERRIDES_BRANCH + location.hostname]
]
}, callback);
});
});
}
function testPriority(callback) {
// foo.bar.com override should have priority over bar.com override
var tests = [
['example.org', 'test1.example.org', 'sub1.test1.example.org'],
['example.org', 'test1.example.org', 'sub2.test1.example.org'],
['example.org', 'test2.example.org', 'sub1.test2.example.org'],
['example.org', 'test2.example.org', 'sub2.test2.example.org'],
];
(function nextTest() {
var [level0, level1, level2] = tests.shift();
var host = 'http://' + level2;
SpecialPowers.pushPrefEnv({
set: [
[PREF_OVERRIDES_ENABLED, true],
[PREF_OVERRIDES_BRANCH + level1, UA_WHOLE_OVERRIDE]
]
}, function () {
// should use first override at this point
testUAIFrameNoNav(host, UA_WHOLE_EXPECTED, true, 'UA not overridden at step ' + (++step), function() {
// add a second override that should be used
testUA({
domain: level2,
override: UA_PARTIAL_OVERRIDE,
test_hosts: [host],
expected: UA_PARTIAL_EXPECTED
}, function () {
// add a third override that should not be used
testUA({
domain: level0,
override: UA_PARTIAL_OVERRIDE,
test_hosts: [host],
expected: UA_WHOLE_EXPECTED
}, tests.length ? nextTest : callback);
});
});
});
})();
}
function testOverrides(callback) {
SpecialPowers.pushPrefEnv({
set: [[PREF_OVERRIDES_ENABLED, true]]
}, function nextTest() {
testUA(tests.shift(), function() { tests.length ? nextTest() : callback() });
});
}
SimpleTest.waitForExplicitFinish();
SimpleTest.requestCompleteLog();
SimpleTest.requestLongerTimeout(5);
testOverrides(function() {
testInactive(function() {
testPriority(SimpleTest.finish)
});
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,357 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=897221
-->
<head>
<title>Test for User Agent Updates</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897221">Mozilla Bug 897221</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<script class="testbody" type="text/javascript">
const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
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_TIMEOUT = PREF_UPDATES + "timeout";
const DEFAULT_UA = navigator.userAgent;
const UA_OVERRIDE = "DummyUserAgent";
const UA_ALT_OVERRIDE = "AltUserAgent";
const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
const UA_PARTIAL_SEP = "#";
const UA_PARTIAL_TO = UA_OVERRIDE;
const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
function getUA(host) {
var url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // sync request
xhr.send();
is(xhr.status, 200, 'request failed');
is(typeof xhr.response, 'string', 'invalid response');
return xhr.response;
}
function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
let url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
let ifr = document.createElement('IFRAME');
ifr.src = url;
document.getElementById('content').appendChild(ifr);
window.addEventListener("message", function recv(e) {
ok(sameQ == (e.data.header.includes(expected)), message);
if (testNavQ) {
ok(navSameQ == (e.data.nav.includes(expected)), navMessage);
}
window.removeEventListener("message", recv);
callback();
});
}
function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
}
const OVERRIDES = [
{
domain: 'example.org',
override: '%DATE%',
host: 'http://example.org'
},
{
domain: 'test1.example.org',
override: '%PRODUCT%',
expected: SpecialPowers.Services.appinfo.name,
host: 'http://test1.example.org'
},
{
domain: 'test2.example.org',
override: '%APP_ID%',
expected: SpecialPowers.Services.appinfo.ID,
host: 'http://test2.example.org'
},
{
domain: 'sub1.test1.example.org',
override: '%APP_VERSION%',
expected: SpecialPowers.Services.appinfo.version,
host: 'http://sub1.test1.example.org'
},
{
domain: 'sub2.test1.example.org',
override: '%BUILD_ID%',
expected: SpecialPowers.Services.appinfo.appBuildID,
host: 'http://sub2.test1.example.org'
},
{
domain: 'sub1.test2.example.org',
override: '%OS%',
expected: SpecialPowers.Services.appinfo.OS,
host: 'http://sub1.test2.example.org'
},
{
domain: 'sub2.test2.example.org',
override: UA_PARTIAL_OVERRIDE,
expected: UA_PARTIAL_EXPECTED,
host: 'http://sub2.test2.example.org'
},
];
function getServerURL() {
var url = location.pathname;
return location.origin + url.slice(0, url.lastIndexOf('/')) + '/user_agent_update.sjs?';
}
function getUpdateURL() {
var url = getServerURL();
var overrides = {};
overrides[location.hostname] = UA_OVERRIDE;
OVERRIDES.forEach(function (val) {
overrides[val.domain] = val.override;
});
url = url + encodeURIComponent(JSON.stringify(overrides)).replace(/%25/g, '%');
return url;
}
function testDownload(callback) {
var startTime = Date.now();
var url = getUpdateURL();
isnot(navigator.userAgent, UA_OVERRIDE, 'UA already overridden');
info('Waiting for UA update: ' + url);
chromeScript.sendAsyncMessage("notify-on-update");
SpecialPowers.pushPrefEnv({
set: [
[PREF_UPDATES_ENABLED, true],
[PREF_UPDATES_URL, url],
[PREF_UPDATES_TIMEOUT, 10000],
[PREF_UPDATES_INTERVAL, 1] // 1 second interval
]
});
function waitForUpdate() {
info("Update Happened");
testUAIFrameNoNav(location.origin, UA_OVERRIDE, true, 'Header UA not overridden', function() {
var updateTime = parseInt(getUA('http://example.org'));
todo(startTime <= updateTime, 'Update was before start time');
todo(updateTime <= Date.now(), 'Update was after present time');
let overs = OVERRIDES;
(function nextOverride() {
val = overs.shift();
if (val.expected) {
testUAIFrameNoNav(val.host, val.expected, true, 'Incorrect URL parameter: ' + val.override, function() {
overs.length ? nextOverride() : callback();
});
} else {
nextOverride();
}
})();
});
}
chromeScript.addMessageListener("useragent-update-complete", waitForUpdate);
}
function testBadUpdate(callback) {
var url = getServerURL() + 'invalid-json';
var prevOverride = navigator.userAgent;
SpecialPowers.pushPrefEnv({
set: [
[PREF_UPDATES_URL, url],
[PREF_UPDATES_INTERVAL, 1] // 1 second interval
]
}, function () { setTimeout(function () {
var ifr = document.createElement('IFRAME');
ifr.src = "about:blank";
ifr.addEventListener('load', function() {
// We want to make sure a bad update doesn't cancel out previous
// overrides. We do this by waiting for 5 seconds (assuming the update
// occurs within 5 seconds), and check that the previous override hasn't
// changed.
is(navigator.userAgent, prevOverride,
'Invalid update deleted previous override');
callback();
});
document.getElementById('content').appendChild(ifr);
}, 5000); });
}
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("Test sets timeouts to wait for updates to happen.");
SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
]
}, function () {
// Sets the OVERRIDES var in the chrome script.
// We do this to avoid code duplication.
chromeScript.sendAsyncMessage("set-overrides", OVERRIDES);
// testProfileLoad, testDownload, and testProfileSave must run in this order
// because testDownload depends on testProfileLoad and testProfileSave depends
// on testDownload to save overrides to the profile
chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
});
const chromeScript = SpecialPowers.loadChromeScript(_ => {
// Enter update timer manager test mode
Cc["@mozilla.org/updates/timer-manager;1"].getService(
Ci.nsIObserver).observe(null, "utm-test-init", "");
var _notifyOnUpdate = false;
const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
var FU = FileUtils;
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
var OSF = OS.File;
const KEY_PREFDIR = "PrefD";
const KEY_APPDIR = "XCurProcD";
const FILE_UPDATES = "ua-update.json";
const UA_OVERRIDE = "DummyUserAgent";
const UA_ALT_OVERRIDE = "AltUserAgent";
const PREF_UPDATES = "general.useragent.updates.";
const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
Services.prefs.addObserver(PREF_UPDATES_LASTUPDATED, () => {
if (_notifyOnUpdate) {
_notifyOnUpdate = false; // Only notify once, for the first update.
sendAsyncMessage("useragent-update-complete");
}
});
var OVERRIDES = null;
function is(value, expected, message) {
sendAsyncMessage("is-message", {value, expected, message});
}
function info(message) {
sendAsyncMessage("info-message", message);
}
function testProfileSave(hostname) {
info('Waiting for saving to profile');
var file = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
(function waitForSave() {
OSF.exists(file).then(
(exists) => {
if (!exists) {
setTimeout(waitForSave, 100);
return;
}
return OSF.read(file).then(
(bytes) => {
info('Saved new overrides');
var decoder = new TextDecoder();
var overrides = JSON.parse(decoder.decode(bytes));
is(overrides[hostname], UA_OVERRIDE, 'Incorrect saved override');
OVERRIDES.forEach(function (val) {
val.expected && is(overrides[val.domain], val.expected,
'Incorrect saved override: ' + val.override);
});
sendAsyncMessage("testProfileSaveDone");
}
);
}
).catch(
(reason) => {
throw reason
}
);
})();
}
function testProfileLoad(hostname) {
var file = FU.getFile(KEY_APPDIR, [FILE_UPDATES]).path;
var encoder = new TextEncoder();
var overrides = {};
overrides[hostname] = UA_ALT_OVERRIDE;
var bytes = encoder.encode(JSON.stringify(overrides));
var badfile = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
var badbytes = encoder.encode("null");
OSF.writeAtomic(file, bytes, {tmpPath: file + ".tmp"}).then(
() => OSF.writeAtomic(badfile, badbytes, {tmpPath: badfile + ".tmp"})
).then(
() => {
sendAsyncMessage("testProfileLoadDone");
},
(reason) => {
throw reason
}
);
}
addMessageListener("testProfileSave", testProfileSave);
addMessageListener("testProfileLoad", testProfileLoad);
addMessageListener("set-overrides", function(overrides) { OVERRIDES = overrides});
addMessageListener("notify-on-update", () => { _notifyOnUpdate = true });
}, { wantGlobalProperties: ["ChromeUtils", "TextEncoder", "TextDecoder"]});
chromeScript.addMessageListener("testProfileSaveDone", SimpleTest.finish);
chromeScript.addMessageListener("testProfileLoadDone", function() {
SpecialPowers.pushPrefEnv({
set: [[PREF_UPDATES_ENABLED, true]]
}, function () {
(function waitForLoad() {
var ifr = document.createElement('IFRAME');
ifr.src = location.origin;
ifr.addEventListener('load', function() {
var nav = ifr.contentWindow.navigator;
if (nav.userAgent !== UA_ALT_OVERRIDE) {
setTimeout(waitForLoad, 100);
return;
}
testUAIFrameNoNav(location.origin, UA_ALT_OVERRIDE, true, 'Did not apply saved override', function () {
testDownload(function() {
testBadUpdate(function() {
chromeScript.sendAsyncMessage("testProfileSave", location.hostname);
})
})
});
}, true);
document.getElementById('content').appendChild(ifr);
})();
});
});
chromeScript.addMessageListener("is-message", function(params) {
let {value, expected, message} = params;
is(value, expected, message);
});
chromeScript.addMessageListener("info-message", function(message) {
info(message);
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=942470
-->
<head>
<title>Test for Bug 942470</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=942470">Mozilla Bug 942470</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 942470 **/
function getUA(host) {
var url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // sync request
xhr.send();
is(xhr.status, 200, 'request failed');
is(typeof xhr.response, 'string', 'invalid response');
return xhr.response;
}
const UA_OVERRIDE = "DummyUserAgent";
info("User agent is " + navigator.userAgent);
isnot(navigator.userAgent, UA_OVERRIDE,
"navigator.userAgent is not reverted");
isnot(getUA(location.origin), UA_OVERRIDE,
"User-Agent is not reverted");
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,21 @@
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Access-Control-Allow-Origin", "*", false);
// used by test_user_agent tests
response.write(
"<html><body>\
<script type='text/javascript'>\
var msg = {\
header: '" + request.getHeader('User-Agent') + "',\
nav: navigator.userAgent\
};\
self.parent.postMessage(msg, '*');\
</script>\
</body></html>"
);
}

Просмотреть файл

@ -0,0 +1,10 @@
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Type", "application/json", false);
// used by test_user_agent_updates test
response.write(decodeURIComponent(request.queryString));
}

Просмотреть файл

@ -36,6 +36,7 @@ RELEASE_SIGN_ANDROID_APK = \
ROOT_FILES := \
application.ini \
package-name.txt \
ua-update.json \
platform.ini \
removed-files \
$(NULL)