зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1510569 - Port onSecurityChange from WebProgressChild.jsm to C++; remove WebProgressChild r=Ehsan,ochameau
This is the last message that WebProgressChild was sending to the RemoteWebProgress in the parent process, so we can remove the module entirely. Differential Revision: https://phabricator.services.mozilla.com/D35091 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
4ab0fd7d38
Коммит
e0d50ea7ce
|
@ -49,7 +49,6 @@ const whitelist = {
|
|||
"resource://gre/modules/ActorManagerChild.jsm",
|
||||
"resource://gre/modules/E10SUtils.jsm",
|
||||
"resource://gre/modules/Readerable.jsm",
|
||||
"resource://gre/modules/WebProgressChild.jsm",
|
||||
|
||||
// Telemetry
|
||||
"resource://gre/modules/TelemetryController.jsm", // bug 1470339
|
||||
|
|
|
@ -103,7 +103,6 @@ function tunnelToInnerBrowser(outer, inner) {
|
|||
|
||||
onLocationChange: (webProgress, request, location, flags) => {
|
||||
if (webProgress && webProgress.isTopLevel) {
|
||||
inner._securityUI = outer._securityUI;
|
||||
inner._documentURI = outer._documentURI;
|
||||
inner._documentContentType = outer._documentContentType;
|
||||
inner._contentTitle = outer._contentTitle;
|
||||
|
@ -117,6 +116,10 @@ function tunnelToInnerBrowser(outer, inner) {
|
|||
}
|
||||
},
|
||||
|
||||
// We do not need an onSecurityChange handler since the remote security UI
|
||||
// has been copied from the inner (remote) browser to the outer (non-remote)
|
||||
// browser and they share it.
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsIWebProgressListener,
|
||||
|
@ -464,8 +467,6 @@ MessageManagerTunnel.prototype = {
|
|||
"Link:AddFeed",
|
||||
"Link:AddSearch",
|
||||
"PageStyle:StyleSheets",
|
||||
// Messages sent to RemoteWebProgress.jsm
|
||||
"Content:SecurityChange",
|
||||
// Messages sent to browser.js
|
||||
"DOMTitleChanged",
|
||||
"ImageDocumentLoaded",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
interface nsIContentSecurityPolicy;
|
||||
interface nsIPrincipal;
|
||||
interface nsITransportSecurityInfo;
|
||||
interface nsIURI;
|
||||
interface nsIWebProgress;
|
||||
interface nsIReferrerInfo;
|
||||
|
@ -164,4 +165,18 @@ interface nsIBrowser : nsISupports
|
|||
in boolean aHasRequestContextID,
|
||||
in uint64_t aRequestContextID,
|
||||
in AString aContentType);
|
||||
|
||||
/**
|
||||
* Called by Gecko when a security chang event needs to update the event
|
||||
* state stored in the security UI object stored in the parent process.
|
||||
*
|
||||
* @param aSecurityInfo the transport security information from the content
|
||||
* process
|
||||
* @param aState the flags from the OnSecurityChange event that triggered
|
||||
* this method, as outlined in nsIWebProgressListener
|
||||
* @param aIsSecureContext whether or not the context is secure
|
||||
*/
|
||||
void updateSecurityUIForSecurityChange(in nsITransportSecurityInfo aSecurityInfo,
|
||||
in uint32_t aState,
|
||||
in boolean aIsSecureContext);
|
||||
};
|
||||
|
|
|
@ -528,23 +528,20 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent,
|
|||
nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
|
||||
MOZ_ASSERT(docShell);
|
||||
|
||||
const uint32_t notifyMask =
|
||||
nsIWebProgress::NOTIFY_STATE_ALL | nsIWebProgress::NOTIFY_PROGRESS |
|
||||
nsIWebProgress::NOTIFY_STATUS | nsIWebProgress::NOTIFY_LOCATION |
|
||||
nsIWebProgress::NOTIFY_REFRESH | nsIWebProgress::NOTIFY_CONTENT_BLOCKING;
|
||||
|
||||
mStatusFilter = new nsBrowserStatusFilter();
|
||||
|
||||
RefPtr<nsIEventTarget> eventTarget =
|
||||
TabGroup()->EventTargetFor(TaskCategory::Network);
|
||||
|
||||
mStatusFilter->SetTarget(eventTarget);
|
||||
nsresult rv = mStatusFilter->AddProgressListener(this, notifyMask);
|
||||
nsresult rv =
|
||||
mStatusFilter->AddProgressListener(this, nsIWebProgress::NOTIFY_ALL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
{
|
||||
nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell);
|
||||
rv = webProgress->AddProgressListener(mStatusFilter, notifyMask);
|
||||
rv = webProgress->AddProgressListener(mStatusFilter,
|
||||
nsIWebProgress::NOTIFY_ALL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -3697,8 +3694,59 @@ NS_IMETHODIMP BrowserChild::OnStatusChange(nsIWebProgress* aWebProgress,
|
|||
NS_IMETHODIMP BrowserChild::OnSecurityChange(nsIWebProgress* aWebProgress,
|
||||
nsIRequest* aRequest,
|
||||
uint32_t aState) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<WebProgressData> webProgressData;
|
||||
RequestData requestData;
|
||||
|
||||
MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData,
|
||||
requestData));
|
||||
|
||||
Maybe<WebProgressSecurityChangeData> securityChangeData;
|
||||
|
||||
if (aWebProgress && webProgressData->isTopLevel()) {
|
||||
nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
|
||||
if (!docShell) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
|
||||
{
|
||||
nsCOMPtr<nsISecureBrowserUI> securityUI;
|
||||
MOZ_TRY(docShell->GetSecurityUI(getter_AddRefs(securityUI)));
|
||||
|
||||
if (securityUI) {
|
||||
MOZ_TRY(securityUI->GetSecInfo(getter_AddRefs(securityInfo)));
|
||||
}
|
||||
}
|
||||
|
||||
bool isSecureContext = false;
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell);
|
||||
if (!outerWindow) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (nsPIDOMWindowInner* window = outerWindow->GetCurrentInnerWindow()) {
|
||||
isSecureContext = window->IsSecureContext();
|
||||
} else {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
securityChangeData.emplace();
|
||||
securityChangeData->securityInfo() = securityInfo.forget();
|
||||
securityChangeData->isSecureContext() = isSecureContext;
|
||||
}
|
||||
|
||||
Unused << SendOnSecurityChange(webProgressData, requestData, aState,
|
||||
securityChangeData);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP BrowserChild::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
|
||||
nsIRequest* aRequest,
|
||||
uint32_t aEvent) {
|
||||
|
|
|
@ -2565,6 +2565,35 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnStatusChange(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserParent::RecvOnSecurityChange(
|
||||
const Maybe<WebProgressData>& aWebProgressData,
|
||||
const RequestData& aRequestData, const uint32_t aState,
|
||||
const Maybe<WebProgressSecurityChangeData>& aSecurityChangeData) {
|
||||
nsCOMPtr<nsIBrowser> browser;
|
||||
nsCOMPtr<nsIWebProgress> manager;
|
||||
nsCOMPtr<nsIWebProgressListener> managerAsListener;
|
||||
if (!GetWebProgressListener(getter_AddRefs(browser), getter_AddRefs(manager),
|
||||
getter_AddRefs(managerAsListener))) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWebProgress> webProgress;
|
||||
nsCOMPtr<nsIRequest> request;
|
||||
ReconstructWebProgressAndRequest(manager, aWebProgressData, aRequestData,
|
||||
getter_AddRefs(webProgress),
|
||||
getter_AddRefs(request));
|
||||
|
||||
if (aWebProgressData && aWebProgressData->isTopLevel()) {
|
||||
Unused << browser->UpdateSecurityUIForSecurityChange(
|
||||
aSecurityChangeData->securityInfo(), aState,
|
||||
aSecurityChangeData->isSecureContext());
|
||||
}
|
||||
|
||||
Unused << managerAsListener->OnSecurityChange(webProgress, request, aState);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserParent::RecvOnContentBlockingEvent(
|
||||
const Maybe<WebProgressData>& aWebProgressData,
|
||||
const RequestData& aRequestData, const uint32_t& aEvent) {
|
||||
|
|
|
@ -311,6 +311,11 @@ class BrowserParent final : public PBrowserParent,
|
|||
const RequestData& aRequestData, const nsresult aStatus,
|
||||
const nsString& aMessage);
|
||||
|
||||
mozilla::ipc::IPCResult RecvOnSecurityChange(
|
||||
const Maybe<WebProgressData>& aWebProgressData,
|
||||
const RequestData& aRequestData, const uint32_t aState,
|
||||
const Maybe<WebProgressSecurityChangeData>& aSecurityChangeData);
|
||||
|
||||
mozilla::ipc::IPCResult RecvOnContentBlockingEvent(
|
||||
const Maybe<WebProgressData>& aWebProgressData,
|
||||
const RequestData& aRequestData, const uint32_t& aEvent);
|
||||
|
|
|
@ -30,6 +30,7 @@ include PBackgroundSharedTypes;
|
|||
|
||||
include "mozilla/GfxMessageUtils.h";
|
||||
include "mozilla/layers/LayersMessageUtils.h";
|
||||
include "mozilla/ipc/TransportSecurityInfoUtils.h";
|
||||
|
||||
using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
|
||||
using mozilla::gfx::MaybeMatrix4x4 from "mozilla/gfx/Matrix.h";
|
||||
|
@ -91,6 +92,7 @@ using mozilla::ScrollAxis from "mozilla/PresShellForwards.h";
|
|||
using mozilla::ScrollFlags from "mozilla/PresShellForwards.h";
|
||||
using struct InputFormData from "mozilla/dom/SessionStoreMessageUtils.h";
|
||||
using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h";
|
||||
using refcounted class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -161,6 +163,12 @@ struct WebProgressLocationChangeData
|
|||
uint64_t? requestContextID;
|
||||
};
|
||||
|
||||
struct WebProgressSecurityChangeData
|
||||
{
|
||||
nsITransportSecurityInfo securityInfo;
|
||||
bool isSecureContext;
|
||||
};
|
||||
|
||||
/**
|
||||
* A PBrowser manages a maximal locally connected subtree of BrowsingContexts
|
||||
* in a content process.
|
||||
|
@ -600,6 +608,10 @@ parent:
|
|||
RequestData aRequestData, nsresult aStatus,
|
||||
nsString aMessage);
|
||||
|
||||
async OnSecurityChange(WebProgressData? aWebProgressData,
|
||||
RequestData aRequestData, uint32_t aState,
|
||||
WebProgressSecurityChangeData? aSecurityChangeData);
|
||||
|
||||
async OnContentBlockingEvent(WebProgressData? aWebProgressData,
|
||||
RequestData aRequestData, uint32_t aEvent);
|
||||
|
||||
|
|
|
@ -10,12 +10,6 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource://gre/modules/BrowserUtils.jsm"
|
||||
);
|
||||
|
||||
const { WebProgressChild } = ChromeUtils.import(
|
||||
"resource://gre/modules/WebProgressChild.jsm"
|
||||
);
|
||||
|
||||
this.WebProgress = new WebProgressChild(this);
|
||||
|
||||
try {
|
||||
docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
|
|
|
@ -1264,8 +1264,6 @@
|
|||
this.messageManager.addMessageListener("DOMTitleChanged", this);
|
||||
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
|
||||
|
||||
// browser-child messages, such as Content:LocationChange, are handled in
|
||||
// RemoteWebProgress, ensure it is loaded and ready.
|
||||
let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
|
||||
let { RemoteWebProgressManager } = ChromeUtils.import(jsm, {});
|
||||
|
||||
|
@ -1514,6 +1512,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
updateSecurityUIForSecurityChange(aSecurityInfo, aState, aIsSecureContext) {
|
||||
if (this.isRemoteBrowser && this.messageManager) {
|
||||
// Invoking this getter triggers the generation of the underlying object,
|
||||
// which we need to access with ._securityUI, because .securityUI returns
|
||||
// a wrapper that makes _update inaccessible.
|
||||
void this.securityUI;
|
||||
this._securityUI._update(aSecurityInfo, aState, aIsSecureContext);
|
||||
}
|
||||
}
|
||||
|
||||
updateSecurityUIForContentBlockingEvent(aEvent) {
|
||||
if (this.isRemoteBrowser && this.messageManager) {
|
||||
// Invoking this getter triggers the generation of the underlying object,
|
||||
|
|
|
@ -5,17 +5,11 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ["RemoteWebProgressManager"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const RemoteWebProgress = Components.Constructor(
|
||||
"@mozilla.org/dom/remote-web-progress;1",
|
||||
"nsIRemoteWebProgress",
|
||||
"init"
|
||||
);
|
||||
const RemoteWebProgressRequest = Components.Constructor(
|
||||
"@mozilla.org/dom/remote-web-progress-request;1",
|
||||
"nsIRemoteWebProgressRequest",
|
||||
"init"
|
||||
);
|
||||
|
||||
class RemoteWebProgressManager {
|
||||
constructor(aBrowser) {
|
||||
|
@ -29,16 +23,7 @@ class RemoteWebProgressManager {
|
|||
}
|
||||
|
||||
swapBrowser(aBrowser) {
|
||||
if (this._messageManager) {
|
||||
this._messageManager.removeMessageListener(
|
||||
"Content:SecurityChange",
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
this._browser = aBrowser;
|
||||
this._messageManager = aBrowser.messageManager;
|
||||
this._messageManager.addMessageListener("Content:SecurityChange", this);
|
||||
}
|
||||
|
||||
swapListeners(aOtherRemoteWebProgressManager) {
|
||||
|
@ -69,20 +54,6 @@ class RemoteWebProgressManager {
|
|||
);
|
||||
}
|
||||
|
||||
_fixSecInfo(aSecInfo) {
|
||||
let deserialized = null;
|
||||
if (aSecInfo) {
|
||||
let helper = Cc["@mozilla.org/network/serialization-helper;1"].getService(
|
||||
Ci.nsISerializationHelper
|
||||
);
|
||||
|
||||
deserialized = helper.deserializeObject(aSecInfo);
|
||||
deserialized.QueryInterface(Ci.nsITransportSecurityInfo);
|
||||
}
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
setCurrentURI(aURI) {
|
||||
// This function is simpler than nsDocShell::SetCurrentURI since
|
||||
// it doesn't have to deal with child docshells.
|
||||
|
@ -183,56 +154,6 @@ class RemoteWebProgressManager {
|
|||
aEvent
|
||||
);
|
||||
}
|
||||
|
||||
receiveMessage(aMessage) {
|
||||
let json = aMessage.json;
|
||||
let webProgress = null;
|
||||
let isTopLevel = json.webProgress && json.webProgress.isTopLevel;
|
||||
// The top-level WebProgress is always the same, but because we don't
|
||||
// really have a concept of subframes/content we always create a new object
|
||||
// for those.
|
||||
if (json.webProgress) {
|
||||
webProgress = isTopLevel
|
||||
? this._topLevelWebProgress
|
||||
: new RemoteWebProgress(this, isTopLevel);
|
||||
webProgress.update(
|
||||
json.webProgress.DOMWindowID,
|
||||
0,
|
||||
json.webProgress.loadType,
|
||||
json.webProgress.isLoadingDocument
|
||||
);
|
||||
webProgress.QueryInterface(Ci.nsIWebProgress);
|
||||
}
|
||||
|
||||
// The WebProgressRequest object however is always dynamic.
|
||||
let request = null;
|
||||
if (json.requestURI) {
|
||||
request = new RemoteWebProgressRequest(
|
||||
Services.io.newURI(json.requestURI),
|
||||
Services.io.newURI(json.originalRequestURI)
|
||||
);
|
||||
request = request.QueryInterface(Ci.nsIRequest);
|
||||
}
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "Content:SecurityChange":
|
||||
let state = json.state;
|
||||
|
||||
if (isTopLevel) {
|
||||
let secInfo = this._fixSecInfo(json.secInfo);
|
||||
let isSecureContext = json.isSecureContext;
|
||||
|
||||
// Invoking this getter triggers the generation of the underlying object,
|
||||
// which we need to access with ._securityUI, because .securityUI returns
|
||||
// a wrapper that makes _update inaccessible.
|
||||
void this._browser.securityUI;
|
||||
this._browser._securityUI._update(secInfo, state, isSecureContext);
|
||||
}
|
||||
|
||||
this.onSecurityChange(webProgress, request, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoteWebProgressManager.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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 = ["WebProgressChild"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"serializationHelper",
|
||||
"@mozilla.org/network/serialization-helper;1",
|
||||
"nsISerializationHelper"
|
||||
);
|
||||
|
||||
class WebProgressChild {
|
||||
constructor(mm) {
|
||||
this.mm = mm;
|
||||
|
||||
// NOTIFY_PROGRESS, NOTIFY_STATE_ALL, NOTIFY_STATUS, NOTIFY_LOCATION, NOTIFY_REFRESH, and
|
||||
// NOTIFY_CONTENT_BLOCKING are handled by PBrowser.
|
||||
let notifyCode =
|
||||
Ci.nsIWebProgress.NOTIFY_ALL &
|
||||
~Ci.nsIWebProgress.NOTIFY_STATE_ALL &
|
||||
~Ci.nsIWebProgress.NOTIFY_PROGRESS &
|
||||
~Ci.nsIWebProgress.NOTIFY_STATUS &
|
||||
~Ci.nsIWebProgress.NOTIFY_LOCATION &
|
||||
~Ci.nsIWebProgress.NOTIFY_REFRESH &
|
||||
~Ci.nsIWebProgress.NOTIFY_CONTENT_BLOCKING;
|
||||
|
||||
this._filter = Cc[
|
||||
"@mozilla.org/appshell/component/browser-status-filter;1"
|
||||
].createInstance(Ci.nsIWebProgress);
|
||||
this._filter.addProgressListener(this, notifyCode);
|
||||
this._filter.target = this.mm.tabEventTarget;
|
||||
|
||||
let webProgress = this.mm.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this._filter, notifyCode);
|
||||
}
|
||||
|
||||
_requestSpec(aRequest, aPropertyName) {
|
||||
if (!aRequest || !(aRequest instanceof Ci.nsIChannel)) {
|
||||
return null;
|
||||
}
|
||||
return aRequest[aPropertyName].spec;
|
||||
}
|
||||
|
||||
_setupJSON(aWebProgress, aRequest) {
|
||||
if (aWebProgress) {
|
||||
let domWindowID = null;
|
||||
try {
|
||||
domWindowID = aWebProgress.DOMWindowID;
|
||||
} catch (e) {
|
||||
// The DOM Window ID getters above may throw if the inner or outer
|
||||
// windows aren't created yet or are destroyed at the time we're making
|
||||
// this call but that isn't fatal so ignore the exceptions here.
|
||||
}
|
||||
|
||||
aWebProgress = {
|
||||
isTopLevel: aWebProgress.isTopLevel,
|
||||
isLoadingDocument: aWebProgress.isLoadingDocument,
|
||||
loadType: aWebProgress.loadType,
|
||||
DOMWindowID: domWindowID,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
webProgress: aWebProgress || null,
|
||||
requestURI: this._requestSpec(aRequest, "URI"),
|
||||
originalRequestURI: this._requestSpec(aRequest, "originalURI"),
|
||||
};
|
||||
}
|
||||
|
||||
_send(name, data) {
|
||||
this.mm.sendAsyncMessage(name, data);
|
||||
}
|
||||
|
||||
getSecInfoAsString() {
|
||||
let secInfo = this.mm.docShell.securityUI.secInfo;
|
||||
if (secInfo) {
|
||||
return serializationHelper.serializeToString(secInfo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
onSecurityChange(aWebProgress, aRequest, aState) {
|
||||
let json = this._setupJSON(aWebProgress, aRequest);
|
||||
|
||||
json.state = aState;
|
||||
|
||||
if (aWebProgress.isTopLevel) {
|
||||
json.secInfo = this.getSecInfoAsString();
|
||||
json.isSecureContext = this.mm.content.isSecureContext;
|
||||
}
|
||||
|
||||
this._send("Content:SecurityChange", json);
|
||||
}
|
||||
}
|
||||
|
||||
WebProgressChild.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIWebProgressListener",
|
||||
"nsISupportsWeakReference",
|
||||
]);
|
|
@ -229,7 +229,6 @@ EXTRA_JS_MODULES += [
|
|||
'Troubleshoot.jsm',
|
||||
'UpdateUtils.jsm',
|
||||
'WebChannel.jsm',
|
||||
'WebProgressChild.jsm',
|
||||
'ZipUtils.jsm',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче