зеркало из https://github.com/mozilla/pjs.git
Fixes for bug 70348 autoconfig feature
r=bnesse, sr=alecf and a=dbaron
This commit is contained in:
Родитель
e34f882502
Коммит
293528ba7d
|
@ -1,4 +1,4 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* The contents of this file are subject to the Netscape Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
|
@ -40,35 +40,33 @@ NS_IMPL_THREADSAFE_ISUPPORTS4(nsAutoConfig, nsIAutoConfig, nsITimerCallback, nsI
|
|||
|
||||
nsAutoConfig::nsAutoConfig()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
NS_INIT_REFCNT();
|
||||
}
|
||||
|
||||
nsresult nsAutoConfig::Init()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
|
||||
nsresult rv=NS_OK;
|
||||
// member initializers and constructor code
|
||||
|
||||
nsresult rv;
|
||||
mLoaded = PR_FALSE;
|
||||
|
||||
// Registering the object as an observer to the profile-after-change topic
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = observerService->AddObserver(this,NS_LITERAL_STRING("profile-after-change").get());
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (observerService) {
|
||||
rv = observerService->AddObserver(this,
|
||||
NS_LITERAL_STRING("profile-after-change").get());
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
}
|
||||
nsCOMPtr<nsIPrefService> prefs =
|
||||
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = prefs->GetBranch(nsnull,getter_AddRefs(mPrefBranch));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
return NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoConfig::~nsAutoConfig()
|
||||
|
@ -77,9 +75,7 @@ nsAutoConfig::~nsAutoConfig()
|
|||
nsCOMPtr<nsIObserverService> observerService =
|
||||
do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
|
||||
if (observerService)
|
||||
rv = observerService->RemoveObserver(this,
|
||||
NS_LITERAL_STRING("profile-after-change").get());
|
||||
|
||||
rv = observerService->RemoveObserver(this,NS_LITERAL_STRING("profile-after-change").get());
|
||||
}
|
||||
|
||||
|
||||
|
@ -99,16 +95,13 @@ nsAutoConfig::OnDataAvailable(nsIRequest* request,
|
|||
{
|
||||
PRUint32 amt, size;
|
||||
nsresult rv;
|
||||
char buf[1025];
|
||||
char buf[1024];
|
||||
|
||||
while (aLength) {
|
||||
size = PR_MIN(aLength, sizeof(buf));
|
||||
rv = aIStream->Read(buf, size, &amt);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
|
||||
"The stream should never block.");
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
}
|
||||
mBuf.Append(buf,amt);
|
||||
aLength -= amt;
|
||||
}
|
||||
|
@ -120,7 +113,6 @@ NS_IMETHODIMP
|
|||
nsAutoConfig::OnStopRequest(nsIRequest* request, nsISupports* context,
|
||||
nsresult aStatus)
|
||||
{
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// If the request is failed, go read the failover.jsc file
|
||||
|
@ -138,27 +130,34 @@ nsAutoConfig::OnStopRequest(nsIRequest* request, nsISupports* context,
|
|||
}
|
||||
|
||||
// Send the autoconfig.jsc to javascript engine.
|
||||
PRBool success = PREF_EvaluateConfigScript(mBuf.get(), mBuf.Length(),nsnull,
|
||||
PR_FALSE,PR_TRUE,PR_FALSE);
|
||||
PRBool success = PREF_EvaluateConfigScript(mBuf.get(), mBuf.Length(),
|
||||
nsnull, PR_FALSE,PR_TRUE,
|
||||
PR_FALSE);
|
||||
if (success) {
|
||||
rv = writeFailoverFile(); /* Write the autoconfig.jsc to
|
||||
failover.jsc (cached copy) */
|
||||
|
||||
// Write the autoconfig.jsc to failover.jsc (cached copy)
|
||||
rv = writeFailoverFile();
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
NS_WARNING("Error writing failover.jsc file");
|
||||
|
||||
// Clean up the previous read.
|
||||
// If there is a timer, these methods will be called again.
|
||||
mBuf.Truncate(0);
|
||||
mLoaded = PR_TRUE; //Releasing the deadlock
|
||||
|
||||
// Releasing the lock to allow the main thread to start execution
|
||||
mLoaded = PR_TRUE;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
else {
|
||||
// there is an error in parsing of the autoconfig file.
|
||||
NS_WARNING("Error reading autoconfig.jsc from the network, reading the offline version");
|
||||
|
||||
// Clean up the previous read so it will be ready for
|
||||
// the next updated read.
|
||||
mBuf.Truncate(0);
|
||||
return readOfflineFile();
|
||||
}
|
||||
}
|
||||
|
||||
// Notify method as a TimerCallBack function
|
||||
NS_IMETHODIMP_(void) nsAutoConfig::Notify(nsITimer *timer)
|
||||
|
@ -170,36 +169,34 @@ NS_IMETHODIMP_(void) nsAutoConfig::Notify(nsITimer *timer)
|
|||
after the profile is set. It doesn't do anything but return NS_OK during the
|
||||
creation time. Second time it calls DownloadAutoCfg().
|
||||
*/
|
||||
NS_IMETHODIMP nsAutoConfig::Observe(nsISupports *aSubject, const PRUnichar *aTopic, const PRUnichar *someData)
|
||||
|
||||
NS_IMETHODIMP nsAutoConfig::Observe(nsISupports *aSubject,
|
||||
const PRUnichar *aTopic,
|
||||
const PRUnichar *someData)
|
||||
{
|
||||
nsresult rv=NS_OK;
|
||||
if (!nsCRT::strcmp(aTopic, NS_LITERAL_STRING("profile-after-change").get()))
|
||||
{
|
||||
if (!nsCRT::strcmp(aTopic,
|
||||
NS_LITERAL_STRING("profile-after-change").get())) {
|
||||
|
||||
// Getting the current profile name since we already have the
|
||||
// pointer to the object.
|
||||
nsCOMPtr<nsIProfile> profile = do_QueryInterface(aSubject);
|
||||
if (profile) {
|
||||
nsXPIDLString profileName;
|
||||
rv = profile->GetCurrentProfile(getter_Copies(profileName));
|
||||
if (NS_SUCCEEDED(rv))
|
||||
// setting the member variable to the current profile name
|
||||
mCurrProfile.AssignWithConversion(profileName);
|
||||
else
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
// setting the member variable to the current profile name
|
||||
mCurrProfile = NS_ConvertUCS2toUTF8(profileName);
|
||||
}
|
||||
return DownloadAutoCfg();
|
||||
rv = DownloadAutoCfg();
|
||||
}
|
||||
else if (!nsCRT::strcmp(aTopic, NS_LITERAL_STRING("app-startup").get()))
|
||||
{
|
||||
// This is the object instantiation, do nothing and return NS_OK;
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsAutoConfig::DownloadAutoCfg()
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
nsresult rv;
|
||||
nsCAutoString emailAddr;
|
||||
nsXPIDLCString urlName;
|
||||
PRBool appendMail=PR_FALSE, offline=PR_FALSE;
|
||||
|
@ -210,16 +207,16 @@ NS_IMETHODIMP nsAutoConfig::DownloadAutoCfg()
|
|||
rv = mPrefBranch->GetCharPref("autoadmin.global_config_url",
|
||||
getter_Copies(urlName));
|
||||
if (NS_FAILED(rv) || (nsCRT::strlen(urlName) == 0))
|
||||
return NS_OK; /* Return ok if there is no config url set. */
|
||||
return NS_OK; // Return ok if there is no config url set.
|
||||
|
||||
// Check to see if the network is online/offline
|
||||
|
||||
nsCOMPtr<nsIIOService> ios =
|
||||
do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = ios->GetOffline(&offline);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (offline) {
|
||||
|
||||
|
@ -248,9 +245,11 @@ NS_IMETHODIMP nsAutoConfig::DownloadAutoCfg()
|
|||
if (NS_SUCCEEDED(rv) && appendMail) {
|
||||
rv = getEmailAddr(emailAddr);
|
||||
if (NS_SUCCEEDED(rv) && emailAddr) {
|
||||
|
||||
/* Adding the unique identifier at the end of autoconfig URL.
|
||||
In this case the autoconfig URL is a script and
|
||||
emailAddr as passed as an argument */
|
||||
emailAddr as passed as an argument
|
||||
*/
|
||||
cfgUrl.Append("?");
|
||||
cfgUrl.Append(emailAddr);
|
||||
}
|
||||
|
@ -258,30 +257,32 @@ NS_IMETHODIMP nsAutoConfig::DownloadAutoCfg()
|
|||
|
||||
// Getting an event queue. If we start an AsyncOpen, the thread
|
||||
// needs to wait before the reading of autoconfig is done
|
||||
|
||||
nsCOMPtr<nsIEventQueue> currentThreadQ;
|
||||
|
||||
// We are having the event queue processing only for the startup
|
||||
// It is not needed with the repeating timer.
|
||||
if (firstTime) {
|
||||
nsCOMPtr<nsIEventQueueService> service =
|
||||
do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = service->GetThreadEventQueue(NS_CURRENT_THREAD,getter_AddRefs(currentThreadQ));
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
}
|
||||
mLoaded = PR_FALSE;
|
||||
|
||||
// create a new url
|
||||
|
||||
nsCOMPtr<nsIURI> url;
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
|
||||
rv = NS_NewURI(getter_AddRefs(url), cfgUrl, nsnull, nsnull);
|
||||
if(NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
// open a channel for the url
|
||||
|
||||
rv = NS_OpenURI(getter_AddRefs(channel),url, nsnull, nsnull, nsnull, nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE);
|
||||
if(NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = channel->AsyncOpen(this, nsnull);
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -289,44 +290,45 @@ NS_IMETHODIMP nsAutoConfig::DownloadAutoCfg()
|
|||
return rv;
|
||||
}
|
||||
|
||||
|
||||
// Set a repeating timer if the pref is set.
|
||||
// This is to be done only once.
|
||||
|
||||
if (firstTime) {
|
||||
firstTime = PR_FALSE;
|
||||
PRInt32 minutes = 0;
|
||||
rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval",
|
||||
&minutes);
|
||||
if (NS_SUCCEEDED(rv) && minutes > 0) {
|
||||
|
||||
// Create a new timer and pass this nsAutoConfig
|
||||
// object as a timer callback.
|
||||
nsCOMPtr<nsITimer> timer;
|
||||
timer = do_CreateInstance("@mozilla.org/timer;1",&rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = timer->Init(this, minutes*60*1000, NS_PRIORITY_NORMAL,
|
||||
NS_TYPE_REPEATING_SLACK);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// process events until we're finished. AutoConfig.jsc reading needs
|
||||
// to be finished before the browser starts loading up
|
||||
// We are waiting for the mLoaded which will be set through
|
||||
// onStopRequest or readOfflineFile methods
|
||||
// There is a possibility of deadlock so we need to make sure
|
||||
// that mLoaded will be set to true in any case (success/failure)
|
||||
/* process events until we're finished. AutoConfig.jsc reading needs
|
||||
to be finished before the browser starts loading up
|
||||
We are waiting for the mLoaded which will be set through
|
||||
onStopRequest or readOfflineFile methods
|
||||
There is a possibility of deadlock so we need to make sure
|
||||
that mLoaded will be set to true in any case (success/failure)
|
||||
*/
|
||||
|
||||
PLEvent *event;
|
||||
while (!mLoaded) {
|
||||
rv = currentThreadQ->WaitForEvent(&event);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),"-->nsAutoConfig::DownloadAutoCfg: currentThreadQ->WaitForEvent failed...");
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = currentThreadQ->HandleEvent(event);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv), "-->nsAutoConfig::DownloadAutoCfg: currentThreadQ->HandleEvent failed...");
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
}
|
||||
|
||||
} //first_time
|
||||
|
@ -343,6 +345,12 @@ nsresult nsAutoConfig::readOfflineFile()
|
|||
nsresult rv;
|
||||
PRBool offline;
|
||||
|
||||
/* Releasing the lock to allow main thread to start
|
||||
execution. At this point we do not need to stall
|
||||
the thread since all network activities are done.
|
||||
*/
|
||||
mLoaded = PR_TRUE;
|
||||
|
||||
rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache);
|
||||
|
||||
if (failCache == PR_FALSE) {
|
||||
|
@ -351,41 +359,43 @@ nsresult nsAutoConfig::readOfflineFile()
|
|||
|
||||
nsCOMPtr<nsIIOService> ios =
|
||||
do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return reportError();
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = ios->GetOffline(&offline);
|
||||
if (NS_FAILED(rv)) return reportError();
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (!offline) {
|
||||
rv = ios->SetOffline(PR_TRUE);
|
||||
if (NS_FAILED(rv)) return reportError();
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
}
|
||||
|
||||
// lock the "network.online" prference so user cannot toggle back to
|
||||
// online mode.
|
||||
rv = mPrefBranch->SetBoolPref("network.online", PR_FALSE);
|
||||
if (NS_FAILED(rv)) return reportError();
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
mPrefBranch->LockPref("network.online");
|
||||
|
||||
mLoaded = PR_TRUE;
|
||||
return NS_OK;
|
||||
|
||||
}
|
||||
|
||||
// failCache = true
|
||||
|
||||
// Open the file and read the content.
|
||||
// execute the javascript file
|
||||
/* faiover_to_cached is set to true so
|
||||
Open the file and read the content.
|
||||
execute the javascript file
|
||||
*/
|
||||
|
||||
nsCOMPtr<nsIFile> failoverFile;
|
||||
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
||||
getter_AddRefs(failoverFile));
|
||||
if (NS_FAILED(rv)) return reportError();
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
failoverFile->Append("failover.jsc");
|
||||
rv = evaluateLocalFile(failoverFile);
|
||||
if (NS_FAILED(rv))
|
||||
NS_WARNING("Couldn't open failover.jsc, going back to default prefs");
|
||||
mLoaded = PR_TRUE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -395,7 +405,8 @@ nsresult nsAutoConfig::evaluateLocalFile(nsIFile* file)
|
|||
nsCOMPtr<nsIInputStream> inStr;
|
||||
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
PRInt64 fileSize;
|
||||
PRUint32 fs, amt=0;
|
||||
|
@ -406,17 +417,14 @@ nsresult nsAutoConfig::evaluateLocalFile(nsIFile* file)
|
|||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
rv = inStr->Read(buf, fs, &amt);
|
||||
if (NS_FAILED(rv))
|
||||
NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
|
||||
"The stream should never block.");
|
||||
|
||||
if (!PREF_EvaluateConfigScript(buf,fs,nsnull, PR_FALSE, PR_TRUE, PR_FALSE)){
|
||||
PR_FREEIF(buf);
|
||||
return NS_ERROR_FAILURE;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (!PREF_EvaluateConfigScript(buf,fs,nsnull, PR_FALSE,
|
||||
PR_TRUE, PR_FALSE))
|
||||
rv = NS_ERROR_FAILURE;
|
||||
}
|
||||
inStr->Close();
|
||||
PR_FREEIF(buf);
|
||||
return NS_OK;
|
||||
PR_Free(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsAutoConfig::writeFailoverFile()
|
||||
|
@ -428,12 +436,14 @@ nsresult nsAutoConfig::writeFailoverFile()
|
|||
|
||||
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
||||
getter_AddRefs(failoverFile));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
failoverFile->Append("failover.jsc");
|
||||
|
||||
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = outStr->Write(mBuf.get(),mBuf.Length(),&amt);
|
||||
outStr->Close();
|
||||
return rv;
|
||||
|
@ -462,14 +472,14 @@ nsresult nsAutoConfig::getEmailAddr(nsAWritableCString & emailAddr)
|
|||
nsDependentCString(prefValue, len) + NS_LITERAL_CSTRING(".identities");
|
||||
rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
|
||||
getter_Copies(prefValue));
|
||||
|
||||
// should the following |strlen|s ask |== 0|? |PRUint32| can't be |<0|
|
||||
if (NS_FAILED(rv) || (len = nsCRT::strlen(prefValue)) < 0) return rv;
|
||||
if (NS_FAILED(rv) || (len = nsCRT::strlen(prefValue)) == 0)
|
||||
return rv;
|
||||
emailAddr = NS_LITERAL_CSTRING("mail.identity.") +
|
||||
nsDependentCString(prefValue, len) + NS_LITERAL_CSTRING(".useremail");
|
||||
rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
|
||||
getter_Copies(prefValue));
|
||||
if (NS_FAILED(rv) || (len = nsCRT::strlen(prefValue)) < 0) return rv;
|
||||
if (NS_FAILED(rv) || (len = nsCRT::strlen(prefValue)) == 0)
|
||||
return rv;
|
||||
emailAddr = nsDependentCString(prefValue, len);
|
||||
}
|
||||
else {
|
||||
|
@ -481,10 +491,3 @@ nsresult nsAutoConfig::getEmailAddr(nsAWritableCString & emailAddr)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult nsAutoConfig::reportError()
|
||||
{
|
||||
NS_ERROR("AutoConfig::readOfflineFile() failed");
|
||||
mLoaded = PR_TRUE; //Releasing lock to avoid deadlock in DownloadAutoCfg()
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ class nsAutoConfig : public nsIAutoConfig,
|
|||
nsresult evaluateLocalFile(nsIFile * file);
|
||||
nsresult writeFailoverFile();
|
||||
nsresult getEmailAddr(nsAWritableCString & emailAddr);
|
||||
nsresult reportError();
|
||||
nsCString mBuf, mCurrProfile;
|
||||
nsCOMPtr<nsIPrefBranch> mPrefBranch;
|
||||
PRBool mLoaded;
|
||||
|
|
|
@ -61,7 +61,6 @@ function setOfflineUI(offline)
|
|||
|
||||
//Checking for a preference "network.online", if it's locked, disabling
|
||||
// network icon and menu item
|
||||
try {
|
||||
var prefService = Components.classes["@mozilla.org/preferences-service;1"];
|
||||
prefService = prefService.getService();
|
||||
prefService = prefService.QueryInterface(Components.interfaces.nsIPrefService);
|
||||
|
@ -73,8 +72,6 @@ function setOfflineUI(offline)
|
|||
if (offlineLocked ) {
|
||||
broadcaster.setAttribute("disabled","true");
|
||||
}
|
||||
} catch(e) {
|
||||
}
|
||||
|
||||
var bundle = srGetStrBundle("chrome://communicator/locale/utilityOverlay.properties");
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче