mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Bug 1213632: Prevent WebExtensions from using versioned JavaScript. r=billm
--HG-- extra : commitid : 3aQ4wS5I7LP extra : rebase_source : 252277140f700ab305877f3bfd0ba9b582ff0b7a
This commit is contained in:
parent
db0f60ba3d
commit
9768c79d20
@ -89,6 +89,9 @@
|
|||||||
"@mozilla.org/updates/update-processor;1"
|
"@mozilla.org/updates/update-processor;1"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define NS_ADDONCONTENTPOLICY_CONTRACTID \
|
||||||
|
"@mozilla.org/addons/content-policy;1"
|
||||||
|
|
||||||
#define NS_ADDONPATHSERVICE_CONTRACTID \
|
#define NS_ADDONPATHSERVICE_CONTRACTID \
|
||||||
"@mozilla.org/addon-path-service;1"
|
"@mozilla.org/addon-path-service;1"
|
||||||
|
|
||||||
@ -178,5 +181,8 @@
|
|||||||
#define NS_APPLICATION_REPUTATION_SERVICE_CID \
|
#define NS_APPLICATION_REPUTATION_SERVICE_CID \
|
||||||
{ 0x8576c950, 0xf4a2, 0x11e2, { 0xb7, 0x78, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
|
{ 0x8576c950, 0xf4a2, 0x11e2, { 0xb7, 0x78, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
|
||||||
|
|
||||||
|
#define NS_ADDONCONTENTPOLICY_CID \
|
||||||
|
{ 0xc26a8241, 0xecf4, 0x4aed, { 0x9f, 0x3c, 0xf1, 0xf5, 0xc7, 0x13, 0xb9, 0xa5 } }
|
||||||
|
|
||||||
#define NS_ADDON_PATH_SERVICE_CID \
|
#define NS_ADDON_PATH_SERVICE_CID \
|
||||||
{ 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
|
{ 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "nsBrowserStatusFilter.h"
|
#include "nsBrowserStatusFilter.h"
|
||||||
#include "mozilla/FinalizationWitnessService.h"
|
#include "mozilla/FinalizationWitnessService.h"
|
||||||
#include "mozilla/NativeOSFileInternals.h"
|
#include "mozilla/NativeOSFileInternals.h"
|
||||||
|
#include "mozilla/AddonContentPolicy.h"
|
||||||
#include "mozilla/AddonPathService.h"
|
#include "mozilla/AddonPathService.h"
|
||||||
|
|
||||||
#if defined(XP_WIN)
|
#if defined(XP_WIN)
|
||||||
@ -125,6 +126,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(FinalizationWitnessService, Init)
|
|||||||
NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
|
NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
|
||||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
|
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
|
||||||
|
|
||||||
|
NS_GENERIC_FACTORY_CONSTRUCTOR(AddonContentPolicy)
|
||||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
|
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
|
||||||
|
|
||||||
NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
|
NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
|
||||||
@ -158,6 +160,7 @@ NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
|
|||||||
#endif
|
#endif
|
||||||
NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
|
NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
|
||||||
NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
|
NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
|
||||||
|
NS_DEFINE_NAMED_CID(NS_ADDONCONTENTPOLICY_CID);
|
||||||
NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
|
NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
|
||||||
NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
|
NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
|
||||||
|
|
||||||
@ -192,6 +195,7 @@ static const Module::CIDEntry kToolkitCIDs[] = {
|
|||||||
#endif
|
#endif
|
||||||
{ &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
|
{ &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
|
||||||
{ &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
|
{ &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
|
||||||
|
{ &kNS_ADDONCONTENTPOLICY_CID, false, nullptr, AddonContentPolicyConstructor },
|
||||||
{ &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
|
{ &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
|
||||||
{ &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor },
|
{ &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor },
|
||||||
{ nullptr }
|
{ nullptr }
|
||||||
@ -228,15 +232,22 @@ static const Module::ContractIDEntry kToolkitContracts[] = {
|
|||||||
#endif
|
#endif
|
||||||
{ FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
|
{ FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
|
||||||
{ NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
|
{ NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
|
||||||
|
{ NS_ADDONCONTENTPOLICY_CONTRACTID, &kNS_ADDONCONTENTPOLICY_CID },
|
||||||
{ NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
|
{ NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
|
||||||
{ NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID },
|
{ NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID },
|
||||||
{ nullptr }
|
{ nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const mozilla::Module::CategoryEntry kToolkitCategories[] = {
|
||||||
|
{ "content-policy", NS_ADDONCONTENTPOLICY_CONTRACTID, NS_ADDONCONTENTPOLICY_CONTRACTID },
|
||||||
|
{ nullptr }
|
||||||
|
};
|
||||||
|
|
||||||
static const Module kToolkitModule = {
|
static const Module kToolkitModule = {
|
||||||
Module::kVersion,
|
Module::kVersion,
|
||||||
kToolkitCIDs,
|
kToolkitCIDs,
|
||||||
kToolkitContracts
|
kToolkitContracts,
|
||||||
|
kToolkitCategories
|
||||||
};
|
};
|
||||||
|
|
||||||
NSMODULE_DEFN(nsToolkitCompsModule) = &kToolkitModule;
|
NSMODULE_DEFN(nsToolkitCompsModule) = &kToolkitModule;
|
||||||
|
@ -33,3 +33,5 @@ support-files =
|
|||||||
[test_ext_bookmarks.html]
|
[test_ext_bookmarks.html]
|
||||||
[test_ext_alarms.html]
|
[test_ext_alarms.html]
|
||||||
[test_ext_background_window_properties.html]
|
[test_ext_background_window_properties.html]
|
||||||
|
[test_ext_jsversion.html]
|
||||||
|
skip-if = e10s # Uses a console monitor which doesn't work from a content process. The code being tested doesn't run in a tab content process in any case.
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test for simple WebExtension</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||||
|
<script type="text/javascript" src="head.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
|
||||||
|
add_task(function* test_versioned_js() {
|
||||||
|
// We need to deal with escaping the close script tags.
|
||||||
|
// May as well consolidate it into one place.
|
||||||
|
let script = attrs => `<script ${attrs}></${'script'}>`;
|
||||||
|
|
||||||
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
"background": {"page": "background.html"}
|
||||||
|
},
|
||||||
|
|
||||||
|
files: {
|
||||||
|
"background.html": `
|
||||||
|
<meta charset="utf-8">
|
||||||
|
${script('src="background.js" type="application/javascript"')}
|
||||||
|
${script('src="background-1.js" type="application/javascript;version=1.8"')}
|
||||||
|
${script('src="background-2.js" type="application/javascript;version=latest"')}
|
||||||
|
${script('src="background-3.js" type="application/javascript"')}
|
||||||
|
`,
|
||||||
|
|
||||||
|
"background.js": "new " + function () {
|
||||||
|
browser.runtime.onMessage.addListener(msg => {
|
||||||
|
browser.test.assertEq(
|
||||||
|
msg, "background-script-3",
|
||||||
|
"Expected a message only from the unversioned background script.");
|
||||||
|
|
||||||
|
browser.test.sendMessage("finished");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
"background-1.js": "new " + function () {
|
||||||
|
browser.runtime.sendMessage("background-script-1");
|
||||||
|
},
|
||||||
|
"background-2.js": "new " + function () {
|
||||||
|
browser.runtime.sendMessage("background-script-2");
|
||||||
|
},
|
||||||
|
"background-3.js": "new " + function () {
|
||||||
|
browser.runtime.sendMessage("background-script-3");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let messages = [/Versioned JavaScript.*not supported in WebExtension.*developer\.mozilla\.org/,
|
||||||
|
/Versioned JavaScript.*not supported in WebExtension.*developer\.mozilla\.org/];
|
||||||
|
|
||||||
|
let waitForConsole = new Promise(resolve => {
|
||||||
|
SimpleTest.monitorConsole(resolve, messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("loading extension");
|
||||||
|
|
||||||
|
yield Promise.all([extension.startup(),
|
||||||
|
extension.awaitMessage("finished")]);
|
||||||
|
|
||||||
|
info("waiting for console");
|
||||||
|
|
||||||
|
SimpleTest.endMonitorConsole();
|
||||||
|
yield waitForConsole;
|
||||||
|
|
||||||
|
info("unloading extension");
|
||||||
|
|
||||||
|
yield extension.unload();
|
||||||
|
|
||||||
|
info("test complete");
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
145
toolkit/mozapps/extensions/AddonContentPolicy.cpp
Normal file
145
toolkit/mozapps/extensions/AddonContentPolicy.cpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#include "AddonContentPolicy.h"
|
||||||
|
|
||||||
|
#include "nsCOMPtr.h"
|
||||||
|
#include "nsContentPolicyUtils.h"
|
||||||
|
#include "nsContentTypeParser.h"
|
||||||
|
#include "nsContentUtils.h"
|
||||||
|
#include "nsIConsoleService.h"
|
||||||
|
#include "nsIContent.h"
|
||||||
|
#include "nsIDocument.h"
|
||||||
|
#include "nsIScriptError.h"
|
||||||
|
#include "nsIURI.h"
|
||||||
|
|
||||||
|
/* Enforces content policies for WebExtension scopes. Currently:
|
||||||
|
*
|
||||||
|
* - Prevents loading scripts with a non-default JavaScript version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define VERSIONED_JS_BLOCKED_MESSAGE \
|
||||||
|
MOZ_UTF16("Versioned JavaScript is a non-standard, deprecated extension, and is ") \
|
||||||
|
MOZ_UTF16("not supported in WebExtension code. For alternatives, please see: ") \
|
||||||
|
MOZ_UTF16("https://developer.mozilla.org/Add-ons/WebExtensions/Tips")
|
||||||
|
|
||||||
|
AddonContentPolicy::AddonContentPolicy()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonContentPolicy::~AddonContentPolicy()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy)
|
||||||
|
|
||||||
|
static nsresult
|
||||||
|
GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult)
|
||||||
|
{
|
||||||
|
NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aContext);
|
||||||
|
NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIDocument> document = content->OwnerDoc();
|
||||||
|
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
nsCOMPtr<nsPIDOMWindow> window = document->GetInnerWindow();
|
||||||
|
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
*aResult = window->WindowID();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static nsresult
|
||||||
|
LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample,
|
||||||
|
nsISupports* aContext)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIScriptError> error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
||||||
|
NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
|
||||||
|
nsAutoCString sourceName;
|
||||||
|
nsresult rv = aSourceURI->GetSpec(sourceName);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
uint64_t windowID = 0;
|
||||||
|
GetWindowIDFromContext(aContext, &windowID);
|
||||||
|
|
||||||
|
rv = error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName),
|
||||||
|
aSourceSample, 0, 0, nsIScriptError::errorFlag,
|
||||||
|
"JavaScript", windowID);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIConsoleService> console = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
||||||
|
NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
|
||||||
|
console->LogMessage(error);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
AddonContentPolicy::ShouldLoad(uint32_t aContentType,
|
||||||
|
nsIURI* aContentLocation,
|
||||||
|
nsIURI* aRequestOrigin,
|
||||||
|
nsISupports* aContext,
|
||||||
|
const nsACString& aMimeTypeGuess,
|
||||||
|
nsISupports* aExtra,
|
||||||
|
nsIPrincipal* aRequestPrincipal,
|
||||||
|
int16_t* aShouldLoad)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
|
||||||
|
"We should only see external content policy types here.");
|
||||||
|
|
||||||
|
*aShouldLoad = nsIContentPolicy::ACCEPT;
|
||||||
|
|
||||||
|
if (!aRequestOrigin) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply this policy to requests from documents loaded from
|
||||||
|
// moz-extension URLs, or to resources being loaded from moz-extension URLs.
|
||||||
|
bool equals;
|
||||||
|
if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) ||
|
||||||
|
(NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aContentType == nsIContentPolicy::TYPE_SCRIPT) {
|
||||||
|
NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess);
|
||||||
|
nsContentTypeParser mimeParser(typeString);
|
||||||
|
|
||||||
|
// Reject attempts to load JavaScript scripts with a non-default version.
|
||||||
|
nsAutoString mimeType, version;
|
||||||
|
if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) &&
|
||||||
|
nsContentUtils::IsJavascriptMIMEType(mimeType) &&
|
||||||
|
NS_SUCCEEDED(mimeParser.GetParameter("version", version))) {
|
||||||
|
*aShouldLoad = nsIContentPolicy::REJECT_REQUEST;
|
||||||
|
|
||||||
|
LogMessage(NS_MULTILINE_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE),
|
||||||
|
aRequestOrigin, typeString, aContext);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
AddonContentPolicy::ShouldProcess(uint32_t aContentType,
|
||||||
|
nsIURI* aContentLocation,
|
||||||
|
nsIURI* aRequestOrigin,
|
||||||
|
nsISupports* aRequestingContext,
|
||||||
|
const nsACString& aMimeTypeGuess,
|
||||||
|
nsISupports* aExtra,
|
||||||
|
nsIPrincipal* aRequestPrincipal,
|
||||||
|
int16_t* aShouldProcess)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
|
||||||
|
"We should only see external content policy types here.");
|
||||||
|
|
||||||
|
*aShouldProcess = nsIContentPolicy::ACCEPT;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
19
toolkit/mozapps/extensions/AddonContentPolicy.h
Normal file
19
toolkit/mozapps/extensions/AddonContentPolicy.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#include "nsIContentPolicy.h"
|
||||||
|
|
||||||
|
class AddonContentPolicy : public nsIContentPolicy
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual ~AddonContentPolicy();
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddonContentPolicy();
|
||||||
|
|
||||||
|
NS_DECL_ISUPPORTS
|
||||||
|
NS_DECL_NSICONTENTPOLICY
|
||||||
|
};
|
@ -49,11 +49,13 @@ if CONFIG['MOZ_EM_DEBUG']:
|
|||||||
JAR_MANIFESTS += ['jar.mn']
|
JAR_MANIFESTS += ['jar.mn']
|
||||||
|
|
||||||
EXPORTS.mozilla += [
|
EXPORTS.mozilla += [
|
||||||
|
'AddonContentPolicy.h',
|
||||||
'AddonPathService.h',
|
'AddonPathService.h',
|
||||||
]
|
]
|
||||||
|
|
||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
'AddonPathService.cpp'
|
'AddonContentPolicy.cpp',
|
||||||
|
'AddonPathService.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
FINAL_LIBRARY = 'xul'
|
FINAL_LIBRARY = 'xul'
|
||||||
|
Loading…
Reference in New Issue
Block a user