Backed out 2 changesets (bug 1148544) for failures in test_user_agent_overrides.html

Backed out changeset 5073d841f77d (bug 1148544)
Backed out changeset 64a2c41c6f20 (bug 1148544)
This commit is contained in:
Wes Kocher 2016-02-09 16:58:54 -08:00
parent 91f156d728
commit 92997a401b
12 changed files with 139 additions and 229 deletions

View File

@ -58,7 +58,7 @@ interface nsILoadGroup : nsIRequest
* By the time this call ends, aRequest will have been removed from the * By the time this call ends, aRequest will have been removed from the
* loadgroup, even if this function throws an exception. * loadgroup, even if this function throws an exception.
*/ */
void removeRequest(in nsIRequest aRequest, void removeRequest(in nsIRequest aRequest,
in nsISupports aContext, in nsISupports aContext,
in nsresult aStatus); in nsresult aStatus);
@ -97,10 +97,4 @@ interface nsILoadGroup : nsIRequest
* the docShell has created the default request.) * the docShell has created the default request.)
*/ */
attribute nsLoadFlags defaultLoadFlags; attribute nsLoadFlags defaultLoadFlags;
/**
* The cached user agent override created by UserAgentOverrides.jsm. Used
* for all sub-resource requests in the loadgroup.
*/
attribute ACString userAgentOverrideCache;
}; };

View File

@ -172,7 +172,7 @@ nsLoadGroup::GetName(nsACString &result)
result.Truncate(); result.Truncate();
return NS_OK; return NS_OK;
} }
return mDefaultLoadRequest->GetName(result); return mDefaultLoadRequest->GetName(result);
} }
@ -188,9 +188,9 @@ nsLoadGroup::GetStatus(nsresult *status)
{ {
if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest)
return mDefaultLoadRequest->GetStatus(status); return mDefaultLoadRequest->GetStatus(status);
*status = mStatus; *status = mStatus;
return NS_OK; return NS_OK;
} }
static bool static bool
@ -480,7 +480,7 @@ nsLoadGroup::AddRequest(nsIRequest *request, nsISupports* ctxt)
rv = MergeLoadFlags(request, flags); rv = MergeLoadFlags(request, flags);
} }
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
// //
// Add the request to the list of active requests... // Add the request to the list of active requests...
// //
@ -810,24 +810,10 @@ nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags)
return NS_OK; 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 void
nsLoadGroup::TelemetryReport() nsLoadGroup::TelemetryReport()
{ {
if (mDefaultLoadIsTimed) { if (mDefaultLoadIsTimed) {

View File

@ -29,7 +29,7 @@ class nsLoadGroup : public nsILoadGroup,
{ {
public: public:
NS_DECL_AGGREGATED NS_DECL_AGGREGATED
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// nsIRequest methods: // nsIRequest methods:
NS_DECL_NSIREQUEST NS_DECL_NSIREQUEST
@ -79,7 +79,7 @@ protected:
nsWeakPtr mObserver; nsWeakPtr mObserver;
nsWeakPtr mParentLoadGroup; nsWeakPtr mParentLoadGroup;
nsresult mStatus; nsresult mStatus;
int32_t mPriority; int32_t mPriority;
bool mIsCanceling; bool mIsCanceling;
@ -92,8 +92,6 @@ protected:
/* For nsPILoadGroupInternal */ /* For nsPILoadGroupInternal */
uint32_t mTimedNonCachedRequestsUntilOnEndPageLoad; uint32_t mTimedNonCachedRequestsUntilOnEndPageLoad;
nsCString mUserAgentOverrideCache;
}; };
#endif // nsLoadGroup_h__ #endif // nsLoadGroup_h__

View File

@ -2464,41 +2464,6 @@ HttpBaseChannel::ShouldIntercept(nsIURI* aURI)
return shouldIntercept; return shouldIntercept;
} }
void
HttpBaseChannel::SetLoadGroupUserAgentOverride()
{
nsCOMPtr<nsIURI> uri;
GetURI(getter_AddRefs(uri));
nsAutoCString uriScheme;
if (uri) {
uri->GetScheme(uriScheme);
}
nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
nsCOMPtr<nsILoadGroup> rootLoadGroup;
if (childLoadGroup) {
childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
}
if (rootLoadGroup && !uriScheme.EqualsLiteral("file")) {
nsAutoCString ua;
if (nsContentUtils::IsNonSubresourceRequest(this)) {
gHttpHandler->OnUserAgentRequest(this);
GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
rootLoadGroup->SetUserAgentOverrideCache(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()) {
rootLoadGroup->GetUserAgentOverrideCache(ua);
SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua, false);
}
}
} else {
// If the root loadgroup doesn't exist or if the channel's URI's scheme is "file",
// fall back on getting the UA override per channel.
gHttpHandler->OnUserAgentRequest(this);
}
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// nsHttpChannel::nsITraceableChannel // nsHttpChannel::nsITraceableChannel
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -353,8 +353,6 @@ protected:
// for a possible synthesized response instead. // for a possible synthesized response instead.
bool ShouldIntercept(nsIURI* aURI = nullptr); bool ShouldIntercept(nsIURI* aURI = nullptr);
void SetLoadGroupUserAgentOverride();
friend class PrivateBrowsingChannel<HttpBaseChannel>; friend class PrivateBrowsingChannel<HttpBaseChannel>;
friend class InterceptFailedOnStop; friend class InterceptFailedOnStop;

View File

@ -1805,12 +1805,9 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
return NS_OK; return NS_OK;
} }
// Set user agent override from docshell // Set user agent override
HttpBaseChannel::SetDocshellUserAgentOverride(); HttpBaseChannel::SetDocshellUserAgentOverride();
// Set user agent override from loadgroup
HttpBaseChannel::SetLoadGroupUserAgentOverride();
MOZ_ASSERT_IF(mPostRedirectChannelShouldUpgrade, MOZ_ASSERT_IF(mPostRedirectChannelShouldUpgrade,
mPostRedirectChannelShouldIntercept); mPostRedirectChannelShouldIntercept);
bool shouldUpgrade = mPostRedirectChannelShouldUpgrade; bool shouldUpgrade = mPostRedirectChannelShouldUpgrade;

View File

@ -46,9 +46,9 @@ this.UserAgentOverrides = {
Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false); Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false);
try { try {
Services.obs.addObserver(HTTP_on_useragent_request, "http-on-useragent-request", false); Services.obs.addObserver(HTTP_on_modify_request, "http-on-modify-request", false);
} catch (x) { } catch (x) {
// The http-on-useragent-request notification is disallowed in content processes. // The http-on-modify-request notification is disallowed in content processes.
} }
UserAgentUpdates.init(function(overrides) { UserAgentUpdates.init(function(overrides) {
@ -118,7 +118,7 @@ this.UserAgentOverrides = {
Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides); Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides);
Services.obs.removeObserver(HTTP_on_useragent_request, "http-on-useragent-request"); Services.obs.removeObserver(HTTP_on_modify_request, "http-on-modify-request");
}, },
receiveMessage: function(aMessage) { receiveMessage: function(aMessage) {
@ -169,7 +169,7 @@ function buildOverrides() {
} }
} }
function HTTP_on_useragent_request(aSubject, aTopic, aData) { function HTTP_on_modify_request(aSubject, aTopic, aData) {
let channel = aSubject.QueryInterface(Ci.nsIHttpChannel); let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
for (let callback of gOverrideFunctions) { for (let callback of gOverrideFunctions) {

View File

@ -5178,11 +5178,6 @@ nsHttpChannel::BeginConnect()
// notify "http-on-modify-request" observers // notify "http-on-modify-request" observers
CallOnModifyRequestObservers(); CallOnModifyRequestObservers();
// If mLoadGroup is null, e10s is enabled and this will be handled by HttpChannelChild.
if (mLoadGroup) {
HttpBaseChannel::SetLoadGroupUserAgentOverride();
}
// Check to see if we should redirect this channel elsewhere by // Check to see if we should redirect this channel elsewhere by
// nsIHttpChannel.redirectTo API request // nsIHttpChannel.redirectTo API request
if (mAPIRedirectToURI) { if (mAPIRedirectToURI) {

View File

@ -275,12 +275,6 @@ public:
NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_TOPIC); NotifyObservers(chan, NS_HTTP_ON_MODIFY_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 once headers are available // Called by the channel once headers are available
void OnExamineResponse(nsIHttpChannel *chan) void OnExamineResponse(nsIHttpChannel *chan)
{ {

View File

@ -113,14 +113,5 @@ interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
*/ */
#define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response" #define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response"
/**
* 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"
%} %}

View File

@ -29,79 +29,96 @@ const UA_PARTIAL_TO = UA_WHOLE_OVERRIDE;
const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO; 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_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
function testUATab(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) { function getUA(host) {
let url = location.pathname; var url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs'; url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
let uri = Services.io.newURI(url, null, null);
let tab = BrowserApp.addTab(uri.spec, { selected: true, parentId: BrowserApp.selectedTab.id });
let browser = tab.browser;
browser.addEventListener("load", function handle() { var xhr = new XMLHttpRequest();
ok(sameQ == (browser.contentDocument.body.innerHTML.indexOf(expected) != -1), message); xhr.open('GET', url, false); // sync request
if(testNavQ) { xhr.send();
ok(navSameQ == (browser.contentWindow.navigator.userAgent.indexOf(expected) != -1), navMessage); is(xhr.status, 200, 'request failed');
} is(typeof xhr.response, 'string', 'invalid response');
browser.removeEventListener("load", handle, true); return xhr.response;
BrowserApp.closeTab(tab);
callback();
}, true);
}
function testUATabNoNav(host, expected, sameQ, message, callback) {
testUATab(host, expected, sameQ, message, false, true, '', callback);
} }
function testUA(options, callback) { function testUA(options, callback) {
var [domain, override, test_hosts, expected] = var [domain, override, test_hosts, expected] =
[options.domain, options.override, options.test_hosts, options.expected]; [options.domain, options.override, options.test_hosts, options.expected];
(function nextTest() { info('Overriding ' + domain + ' with ' + override);
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);
}
function is_subdomain(host) { var localhost = location.origin;
var [test_domain] = host.slice(host.lastIndexOf('/') + 1).split(':', 1); var overrideNavigator = is_subdomain(localhost);
return test_domain === domain || test_domain.endsWith('.' + domain); var navigator_ua, test_ua = [];
}
var localhost = location.origin; // store UA before pref change, to be compared later
var overrideNavigator = is_subdomain(localhost); if (overrideNavigator) {
var navigator_ua, test_ua; navigator_ua = navigator.userAgent;
}
test_hosts.forEach(function (test_host) {
test_ua.push(getUA(test_host));
});
// set the override pref to override the UA
SpecialPowers.pushPrefEnv({
set: [[PREF_OVERRIDES_BRANCH + domain, override]],
}, function () {
var ifr = document.createElement('IFRAME');
ifr.src = "about:blank";
if (overrideNavigator) { ifr.addEventListener('load', function() {
navigator_ua = navigator.userAgent; var nav = ifr.contentWindow.navigator;
}
let url = location.pathname; // check that the UA has changed after pref change
url = test_host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs'; if (overrideNavigator) {
let uri = Services.io.newURI(url, null, null); is(nav.userAgent, expected,
let tab = BrowserApp.addTab(uri.spec, { selected: true, parentId: BrowserApp.selectedTab.id }); 'Navigator UA not overridden at step ' + (++step));
let browser = tab.browser; } else {
is(nav.userAgent, DEFAULT_UA,
'Navigator UA should not be overridden at step ' + (++step));
}
browser.addEventListener("load", function handle() { test_hosts.forEach(function (test_host) {
test_ua = browser.contentDocument.body.innerHTML; is(getUA(test_host), expected,
browser.removeEventListener("load", handle, true); 'Header UA not overridden at step ' + (++step));
BrowserApp.closeTab(tab);
SpecialPowers.pushPrefEnv({
set: [[PREF_OVERRIDES_BRANCH + domain, override]],
}, function () {
testUATab(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 () {
testUATabNoNav(test_host, test_ua, true, 'Header UA not restored at step ' + (++step), function() {
test_hosts.length ? nextTest() : callback();
});
});
});
}); });
}, true);
})(); // clear the override pref to undo overriding the UA
SpecialPowers.pushPrefEnv({
clear: [[PREF_OVERRIDES_BRANCH + domain]],
}, function () {
var ifr = document.createElement('IFRAME');
ifr.src = "about:blank";
ifr.addEventListener('load', function() {
var nav = ifr.contentWindow.navigator;
// check that the UA has changed back
if (overrideNavigator) {
is(nav.userAgent, navigator_ua,
'Navigator UA not restored at step ' + (++step));
}
test_hosts.forEach(function (test_host, i) {
is(getUA(test_host), test_ua[i],
'Header UA not restored at step ' + (++step));
});
callback();
});
document.getElementById('content').appendChild(ifr);
});
}, false);
document.getElementById('content').appendChild(ifr);
});
} }
var step = 0; // for logging var step = 0; // for logging
var tests = [ var tests = [
// should override both header and navigator.userAgent // should override both header and navigator.userAgent
@ -158,15 +175,16 @@ function testInactive(callback) {
[PREF_OVERRIDES_BRANCH + location.hostname, UA_WHOLE_OVERRIDE] [PREF_OVERRIDES_BRANCH + location.hostname, UA_WHOLE_OVERRIDE]
] ]
}, function () { }, function () {
testUATab(location.origin, UA_WHOLE_OVERRIDE, false, 'Failed to disabled header UA override at step ' + (++step), isnot(navigator.userAgent, UA_WHOLE_OVERRIDE,
true, false, 'Failed to disable navigator UA override at step + ' + (++step), function () { 'Failed to disable navigator UA override');
SpecialPowers.pushPrefEnv({ isnot(getUA(location.origin), UA_WHOLE_OVERRIDE,
clear: [ 'Failed to disable header UA override');
[PREF_OVERRIDES_ENABLED], SpecialPowers.pushPrefEnv({
[PREF_OVERRIDES_BRANCH + location.hostname] clear: [
] [PREF_OVERRIDES_ENABLED],
}, callback); [PREF_OVERRIDES_BRANCH + location.hostname]
}); ]
}, callback);
}); });
} }
@ -188,22 +206,22 @@ function testPriority(callback) {
] ]
}, function () { }, function () {
// should use first override at this point // should use first override at this point
testUATabNoNav(host, UA_WHOLE_EXPECTED, true, 'UA not overridden at step ' + (++step), function() { is(getUA(host),
// add a second override that should be used UA_WHOLE_EXPECTED, 'UA not overridden');
// 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({ testUA({
domain: level2, domain: level0,
override: UA_PARTIAL_OVERRIDE, override: UA_PARTIAL_OVERRIDE,
test_hosts: [host], test_hosts: [host],
expected: UA_PARTIAL_EXPECTED expected: UA_WHOLE_EXPECTED
}, function () { }, tests.length ? nextTest : callback);
// 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);
});
}); });
}); });
})(); })();
@ -222,15 +240,9 @@ SpecialPowers.wrap(UserAgentOverrides).init();
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
SimpleTest.requestCompleteLog(); SimpleTest.requestCompleteLog();
SimpleTest.requestLongerTimeout(5);
info(SpecialPowers.Cc["@mozilla.org/dom/site-specific-user-agent;1"].number); info(SpecialPowers.Cc["@mozilla.org/dom/site-specific-user-agent;1"].number);
const Services = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services;
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = chromeWin.BrowserApp;
testOverrides(function() { testOverrides(function() {
testInactive(function() { testInactive(function() {
testPriority(SimpleTest.finish) testPriority(SimpleTest.finish)

View File

@ -48,28 +48,6 @@ function getUA(host) {
return xhr.response; return xhr.response;
} }
function testUATab(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
let url = location.pathname;
url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
let uri = Services.io.newURI(url, null, null);
let tab = BrowserApp.addTab(uri.spec, { selected: true, parentId: BrowserApp.selectedTab.id });
let browser = tab.browser;
browser.addEventListener("load", function handle() {
ok(sameQ == (browser.contentDocument.body.innerHTML.indexOf(expected) != -1), message);
if(testNavQ) {
ok(navSameQ == (browser.contentWindow.navigator.userAgent.indexOf(expected) != -1), navMessage);
}
browser.removeEventListener("load", handle, true);
BrowserApp.closeTab(tab);
callback();
}, true);
}
function testUATabNoNav(host, expected, sameQ, message, callback) {
testUATab(host, expected, sameQ, message, false, true, '', callback);
}
const OVERRIDES = [ const OVERRIDES = [
{ {
domain: 'example.org', domain: 'example.org',
@ -142,25 +120,33 @@ function testDownload(callback) {
[PREF_UPDATES_TIMEOUT, 10000], [PREF_UPDATES_TIMEOUT, 10000],
[PREF_UPDATES_INTERVAL, 1] // 1 second interval [PREF_UPDATES_INTERVAL, 1] // 1 second interval
] ]
}, setTimeout( function waitForUpdate() { }, function waitForUpdate() { setTimeout(function () {
testUATabNoNav(location.origin, UA_OVERRIDE, true, 'Header UA not overridden', function() { var ifr = document.createElement('IFRAME');
var updateTime = parseInt(getUA('http://example.org')); ifr.src = "about:blank";
todo(startTime <= updateTime, 'Update was before start time');
todo(updateTime <= Date.now(), 'Update was after present time');
let overs = OVERRIDES; ifr.addEventListener('load', function() {
(function nextOverride() { var nav = ifr.contentWindow.navigator;
val = overs.shift(); if (nav.userAgent !== UA_OVERRIDE) {
if (val.expected) { waitForUpdate();
testUATabNoNav(val.host, val.expected, true, 'Incorrect URL parameter: ' + val.override, function() { return;
overs.length ? nextOverride() : callback(); }
});
} else { info('Overrode navigator UA');
nextOverride(); is(getUA(location.origin), UA_OVERRIDE, 'Header UA not overridden');
}
})(); var updateTime = parseInt(getUA('http://example.org'));
}); ok(startTime <= updateTime, 'Update was before start time');
}, 2000)); ok(updateTime <= Date.now(), 'Update was after present time');
OVERRIDES.forEach(function (val) {
val.expected && is(getUA(val.host), val.expected,
'Incorrect URL parameter: ' + val.override);
});
callback();
}, false);
document.getElementById('content').appendChild(ifr);
}, 100); });
} }
function testBadUpdate(callback) { function testBadUpdate(callback) {
@ -218,11 +204,10 @@ function testProfileLoad(callback) {
setTimeout(waitForLoad, 100); setTimeout(waitForLoad, 100);
return; return;
} }
testUATabNoNav(location.origin, UA_ALT_OVERRIDE, true, 'Did not apply saved override', function () { is(getUA(location.origin), UA_ALT_OVERRIDE, 'Did not apply saved override');
saveFilePreviousSize = file.fileSize; saveFilePreviousSize = file.fileSize;
callback(); callback();
}); }, false);
}, true);
document.getElementById('content').appendChild(ifr); document.getElementById('content').appendChild(ifr);
})(); })();
@ -275,11 +260,6 @@ var FU = SpecialPowers.wrap(FileUtils);
SpecialPowers.Cu.import("resource://gre/modules/osfile.jsm", window); SpecialPowers.Cu.import("resource://gre/modules/osfile.jsm", window);
var OSF = SpecialPowers.wrap(OS).File; var OSF = SpecialPowers.wrap(OS).File;
const Services = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services;
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = chromeWin.BrowserApp;
// Load UserAgentOverrides.jsm after we load update timer manager // Load UserAgentOverrides.jsm after we load update timer manager
var UAO = null; var UAO = null;