mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 16:25:38 +00:00
Backed out 4 changesets (bug 1513574) for Android build bustage and browser chrome failures. CLOSED TREE
Backed out changeset f155c449e516 (bug 1513574) Backed out changeset 1f2bbbe0f781 (bug 1513574) Backed out changeset 5f8b5f72a4ea (bug 1513574) Backed out changeset 0e4cababccc7 (bug 1513574)
This commit is contained in:
parent
c1ddfa4079
commit
1aeb602d5e
@ -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) {
|
||||
|
@ -696,6 +696,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',
|
||||
|
9
mobile/android/app/ua-update.json.in
Normal file
9
mobile/android/app/ua-update.json.in
Normal file
@ -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",
|
||||
@ -2334,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(
|
||||
@ -4018,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 = {
|
||||
@ -4319,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;
|
||||
@ -4545,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);
|
||||
|
33
netwerk/protocol/http/UAOverridesBootstrapper.jsm
Normal file
33
netwerk/protocol/http/UAOverridesBootstrapper.jsm
Normal file
@ -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"];
|
188
netwerk/protocol/http/UserAgentOverrides.jsm
Normal file
188
netwerk/protocol/http/UserAgentOverrides.jsm
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
333
netwerk/protocol/http/UserAgentUpdates.jsm
Normal file
333
netwerk/protocol/http/UserAgentUpdates.jsm
Normal file
@ -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;
|
||||
}
|
||||
@ -9637,6 +9643,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()) {
|
||||
|
@ -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]
|
||||
|
247
netwerk/test/mochitests/test_user_agent_overrides.html
Normal file
247
netwerk/test/mochitests/test_user_agent_overrides.html
Normal file
@ -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>
|
357
netwerk/test/mochitests/test_user_agent_updates.html
Normal file
357
netwerk/test/mochitests/test_user_agent_updates.html
Normal file
@ -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>
|
44
netwerk/test/mochitests/test_user_agent_updates_reset.html
Normal file
44
netwerk/test/mochitests/test_user_agent_updates_reset.html
Normal file
@ -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>
|
21
netwerk/test/mochitests/user_agent.sjs
Normal file
21
netwerk/test/mochitests/user_agent.sjs
Normal file
@ -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>"
|
||||
);
|
||||
}
|
10
netwerk/test/mochitests/user_agent_update.sjs
Normal file
10
netwerk/test/mochitests/user_agent_update.sjs
Normal file
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user