зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-inbound to mozilla-central. r=merge a=merge
MozReview-Commit-ID: EzZGZI0GBca
This commit is contained in:
Коммит
65706a9c1e
|
@ -801,6 +801,15 @@ DOMInterfaces = {
|
|||
'implicitJSContext': [ 'close' ],
|
||||
},
|
||||
|
||||
'StreamFilter': {
|
||||
'nativeType': 'mozilla::extensions::StreamFilter',
|
||||
},
|
||||
|
||||
'StreamFilterDataEvent': {
|
||||
'nativeType': 'mozilla::extensions::StreamFilterDataEvent',
|
||||
'headerFile': 'mozilla/extensions/StreamFilterEvents.h',
|
||||
},
|
||||
|
||||
'StructuredCloneHolder': {
|
||||
'nativeType': 'mozilla::dom::StructuredCloneBlob',
|
||||
'wrapperCache': False,
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a Mozilla-specific WebExtension API, which is not available to web
|
||||
* content. It allows monitoring and filtering of HTTP response stream data.
|
||||
*
|
||||
* This API should currently be considered experimental, and is not defined by
|
||||
* any standard.
|
||||
*/
|
||||
|
||||
enum StreamFilterStatus {
|
||||
/**
|
||||
* The StreamFilter is not fully initialized. No methods may be called until
|
||||
* a "start" event has been received.
|
||||
*/
|
||||
"uninitialized",
|
||||
/**
|
||||
* The underlying channel is currently transferring data, which will be
|
||||
* dispatched via "data" events.
|
||||
*/
|
||||
"transferringdata",
|
||||
/**
|
||||
* The underlying channel has finished transferring data. Data may still be
|
||||
* written via write() calls at this point.
|
||||
*/
|
||||
"finishedtransferringdata",
|
||||
/**
|
||||
* Data transfer is currently suspended. It may be resumed by a call to
|
||||
* resume(). Data may still be written via write() calls in this state.
|
||||
*/
|
||||
"suspended",
|
||||
/**
|
||||
* The channel has been closed by a call to close(). No further data wlil be
|
||||
* delivered via "data" events, and no further data may be written via
|
||||
* write() calls.
|
||||
*/
|
||||
"closed",
|
||||
/**
|
||||
* The channel has been disconnected by a call to disconnect(). All further
|
||||
* data will be delivered directly, without passing through the filter. No
|
||||
* further events will be dispatched, and no further data may be written by
|
||||
* write() calls.
|
||||
*/
|
||||
"disconnected",
|
||||
/**
|
||||
* An error has occurred and the channel is disconnected. The `error`
|
||||
* property contains the details of the error.
|
||||
*/
|
||||
"failed",
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface which allows an extension to intercept, and optionally modify,
|
||||
* response data from an HTTP request.
|
||||
*/
|
||||
[Exposed=(Window,System),
|
||||
Func="mozilla::extensions::StreamFilter::IsAllowedInContext"]
|
||||
interface StreamFilter : EventTarget {
|
||||
/**
|
||||
* Creates a stream filter for the given add-on and the given extension ID.
|
||||
*/
|
||||
[ChromeOnly]
|
||||
static StreamFilter create(unsigned long long requestId, DOMString addonId);
|
||||
|
||||
/**
|
||||
* Suspends processing of the request. After this is called, no further data
|
||||
* will be delivered until the request is resumed.
|
||||
*/
|
||||
[Throws]
|
||||
void suspend();
|
||||
|
||||
/**
|
||||
* Resumes delivery of data for a suspended request.
|
||||
*/
|
||||
[Throws]
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* Closes the request. After this is called, no more data may be written to
|
||||
* the stream, and no further data will be delivered.
|
||||
*
|
||||
* This *must* be called after the consumer is finished writing data, unless
|
||||
* disconnect() has already been called.
|
||||
*/
|
||||
[Throws]
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Disconnects the stream filter from the request. After this is called, no
|
||||
* further data will be delivered to the filter, and any unprocessed data
|
||||
* will be written directly to the output stream.
|
||||
*/
|
||||
[Throws]
|
||||
void disconnect();
|
||||
|
||||
/**
|
||||
* Writes a chunk of data to the output stream. This may not be called
|
||||
* before the "start" event has been received.
|
||||
*/
|
||||
[Throws]
|
||||
void write((ArrayBuffer or Uint8Array) data);
|
||||
|
||||
/**
|
||||
* Returns the current status of the stream.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute StreamFilterStatus status;
|
||||
|
||||
/**
|
||||
* After an "error" event has been dispatched, this contains a message
|
||||
* describing the error.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute DOMString error;
|
||||
|
||||
/**
|
||||
* Dispatched with a StreamFilterDataEvent whenever incoming data is
|
||||
* available on the stream. This data will not be delivered to the output
|
||||
* stream unless it is explicitly written via a write() call.
|
||||
*/
|
||||
attribute EventHandler ondata;
|
||||
|
||||
/**
|
||||
* Dispatched when the stream is opened, and is about to begin delivering
|
||||
* data.
|
||||
*/
|
||||
attribute EventHandler onstart;
|
||||
|
||||
/**
|
||||
* Dispatched when the stream has closed, and has no more data to deliver.
|
||||
* The output stream remains open and writable until close() is called.
|
||||
*/
|
||||
attribute EventHandler onstop;
|
||||
|
||||
/**
|
||||
* Dispatched when an error has occurred. No further data may be read or
|
||||
* written after this point.
|
||||
*/
|
||||
attribute EventHandler onerror;
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
|
||||
|
||||
/**
|
||||
* This is a Mozilla-specific WebExtension API, which is not available to web
|
||||
* content. It allows monitoring and filtering of HTTP response stream data.
|
||||
*
|
||||
* This API should currently be considered experimental, and is not defined by
|
||||
* any standard.
|
||||
*/
|
||||
|
||||
[Constructor(DOMString type, optional StreamFilterDataEventInit eventInitDict),
|
||||
Func="mozilla::extensions::StreamFilter::IsAllowedInContext",
|
||||
Exposed=(Window,System)]
|
||||
interface StreamFilterDataEvent : Event {
|
||||
/**
|
||||
* Contains a chunk of data read from the input stream.
|
||||
*/
|
||||
[Pure]
|
||||
readonly attribute ArrayBuffer data;
|
||||
};
|
||||
|
||||
dictionary StreamFilterDataEventInit : EventInit {
|
||||
required ArrayBuffer data;
|
||||
};
|
||||
|
|
@ -298,6 +298,9 @@ with Files("SourceBuffer*"):
|
|||
with Files("StereoPannerNode.webidl"):
|
||||
BUG_COMPONENT = ("Core", "Web Audio")
|
||||
|
||||
with Files("StreamFilter*"):
|
||||
BUG_COMPONENT = ("Toolkit", "WebExtensions: Request Handling")
|
||||
|
||||
with Files("Style*"):
|
||||
BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
|
||||
|
||||
|
@ -795,6 +798,8 @@ WEBIDL_FILES = [
|
|||
'StorageEvent.webidl',
|
||||
'StorageManager.webidl',
|
||||
'StorageType.webidl',
|
||||
'StreamFilter.webidl',
|
||||
'StreamFilterDataEvent.webidl',
|
||||
'StructuredCloneHolder.webidl',
|
||||
'StyleSheet.webidl',
|
||||
'StyleSheetList.webidl',
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "mozilla/dom/MessagePortChild.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "mozilla/dom/TabGroup.h"
|
||||
#include "mozilla/extensions/StreamFilterChild.h"
|
||||
#include "mozilla/ipc/IPCStreamAlloc.h"
|
||||
#include "mozilla/ipc/PBackgroundTestChild.h"
|
||||
#include "mozilla/ipc/PChildToParentStreamChild.h"
|
||||
|
@ -426,6 +427,26 @@ BackgroundChildImpl::DeallocPCacheStreamControlChild(PCacheStreamControlChild* a
|
|||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// StreamFilter API
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
extensions::PStreamFilterChild*
|
||||
BackgroundChildImpl::AllocPStreamFilterChild(const uint64_t& aChannelId, const nsString& aAddonId)
|
||||
{
|
||||
RefPtr<extensions::StreamFilterChild> agent = new extensions::StreamFilterChild();
|
||||
return agent.forget().take();
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundChildImpl::DeallocPStreamFilterChild(PStreamFilterChild* aActor)
|
||||
{
|
||||
RefPtr<extensions::StreamFilterChild> child =
|
||||
dont_AddRef(static_cast<extensions::StreamFilterChild*>(aActor));
|
||||
MOZ_ASSERT(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MessageChannel/MessagePort API
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
@ -153,6 +153,13 @@ protected:
|
|||
virtual bool
|
||||
DeallocPMessagePortChild(PMessagePortChild* aActor) override;
|
||||
|
||||
virtual PStreamFilterChild*
|
||||
AllocPStreamFilterChild(const uint64_t& aChannelId,
|
||||
const nsString& aAddonId) override;
|
||||
|
||||
virtual bool
|
||||
DeallocPStreamFilterChild(PStreamFilterChild* aActor) override;
|
||||
|
||||
virtual PChildToParentStreamChild*
|
||||
AllocPChildToParentStreamChild() override;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "mozilla/dom/ipc/PendingIPCBlobParent.h"
|
||||
#include "mozilla/dom/quota/ActorsParent.h"
|
||||
#include "mozilla/dom/StorageIPC.h"
|
||||
#include "mozilla/extensions/StreamFilterParent.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/IPCStreamAlloc.h"
|
||||
|
@ -68,6 +69,7 @@ using mozilla::dom::MessagePortParent;
|
|||
using mozilla::dom::PMessagePortParent;
|
||||
using mozilla::dom::UDPSocketParent;
|
||||
using mozilla::dom::WebAuthnTransactionParent;
|
||||
using mozilla::extensions::StreamFilterParent;
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -715,6 +717,44 @@ BackgroundParentImpl::DeallocPCacheStreamControlParent(PCacheStreamControlParent
|
|||
return true;
|
||||
}
|
||||
|
||||
PStreamFilterParent*
|
||||
BackgroundParentImpl::AllocPStreamFilterParent(const uint64_t& aChannelId, const nsString& aAddonId)
|
||||
{
|
||||
AssertIsInMainProcess();
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
return StreamFilterParent::Create(aChannelId, aAddonId).take();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
BackgroundParentImpl::RecvPStreamFilterConstructor(PStreamFilterParent* aActor,
|
||||
const uint64_t& aChannelId,
|
||||
const nsString& aAddonId)
|
||||
{
|
||||
AssertIsInMainProcess();
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
StreamFilterParent* filter = static_cast<StreamFilterParent*>(aActor);
|
||||
|
||||
RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this);
|
||||
|
||||
filter->Init(parent.forget());
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundParentImpl::DeallocPStreamFilterParent(PStreamFilterParent* aActor)
|
||||
{
|
||||
AssertIsInMainProcess();
|
||||
AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(aActor);
|
||||
|
||||
RefPtr<StreamFilterParent> filter = dont_AddRef(
|
||||
static_cast<StreamFilterParent*>(aActor));
|
||||
return true;
|
||||
}
|
||||
|
||||
PMessagePortParent*
|
||||
BackgroundParentImpl::AllocPMessagePortParent(const nsID& aUUID,
|
||||
const nsID& aDestinationUUID,
|
||||
|
|
|
@ -18,6 +18,8 @@ class VsyncParent;
|
|||
|
||||
namespace ipc {
|
||||
|
||||
using mozilla::extensions::PStreamFilterParent;
|
||||
|
||||
// Instances of this class should never be created directly. This class is meant
|
||||
// to be inherited in BackgroundImpl.
|
||||
class BackgroundParentImpl : public PBackgroundParent
|
||||
|
@ -202,6 +204,18 @@ protected:
|
|||
const nsID& aDestinationUUID,
|
||||
const uint32_t& aSequenceID) override;
|
||||
|
||||
virtual PStreamFilterParent*
|
||||
AllocPStreamFilterParent(const uint64_t& aChannelId,
|
||||
const nsString& aAddonId) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvPStreamFilterConstructor(PStreamFilterParent* aActor,
|
||||
const uint64_t& aChannelId,
|
||||
const nsString& aAddonId) override;
|
||||
|
||||
virtual bool
|
||||
DeallocPStreamFilterParent(PStreamFilterParent* aActor) override;
|
||||
|
||||
virtual PAsmJSCacheEntryParent*
|
||||
AllocPAsmJSCacheEntryParent(const dom::asmjscache::OpenMode& aOpenMode,
|
||||
const dom::asmjscache::WriteParams& aWriteParams,
|
||||
|
|
|
@ -19,6 +19,7 @@ include protocol PHttpBackgroundChannel;
|
|||
include protocol PIPCBlobInputStream;
|
||||
include protocol PPendingIPCBlob;
|
||||
include protocol PMessagePort;
|
||||
include protocol PStreamFilter;
|
||||
include protocol PCameras;
|
||||
include protocol PQuota;
|
||||
include protocol PChildToParentStream;
|
||||
|
@ -68,6 +69,7 @@ sync protocol PBackground
|
|||
manages PIPCBlobInputStream;
|
||||
manages PPendingIPCBlob;
|
||||
manages PMessagePort;
|
||||
manages PStreamFilter;
|
||||
manages PCameras;
|
||||
manages PQuota;
|
||||
manages PChildToParentStream;
|
||||
|
@ -112,6 +114,8 @@ parent:
|
|||
|
||||
async PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
|
||||
|
||||
async PStreamFilter(uint64_t channelId, nsString addonId);
|
||||
|
||||
async PChildToParentStream();
|
||||
|
||||
async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
|
||||
|
|
|
@ -338,15 +338,19 @@ def _makePromise(returns, side, resolver=False):
|
|||
resolvetype = _tuple([d.bareType(side) for d in returns])
|
||||
else:
|
||||
resolvetype = returns[0].bareType(side)
|
||||
|
||||
needmove = not all(d.isCopyable() for d in returns)
|
||||
|
||||
return _promise(resolvetype,
|
||||
_PromiseRejectReason.Type(),
|
||||
ExprLiteral.FALSE, resolver=resolver)
|
||||
ExprLiteral.TRUE if needmove else ExprLiteral.FALSE,
|
||||
resolver=resolver)
|
||||
|
||||
def _makeResolver(returns, side):
|
||||
if len(returns) > 1:
|
||||
resolvetype = _tuple([d.bareType(side) for d in returns])
|
||||
resolvetype = _tuple([d.moveType(side) for d in returns])
|
||||
else:
|
||||
resolvetype = returns[0].bareType(side)
|
||||
resolvetype = returns[0].moveType(side)
|
||||
return TypeFunction([Decl(resolvetype, '')])
|
||||
|
||||
def _cxxArrayType(basetype, const=0, ref=0):
|
||||
|
@ -586,9 +590,14 @@ def _cxxConstRefType(ipdltype, side):
|
|||
t.ref = 1
|
||||
return t
|
||||
|
||||
def _cxxTypeNeedsMove(ipdltype):
|
||||
return ipdltype.isIPDL() and (ipdltype.isArray() or
|
||||
ipdltype.isShmem() or
|
||||
ipdltype.isEndpoint())
|
||||
|
||||
def _cxxMoveRefType(ipdltype, side):
|
||||
t = _cxxBareType(ipdltype, side)
|
||||
if ipdltype.isIPDL() and (ipdltype.isArray() or ipdltype.isShmem() or ipdltype.isEndpoint()):
|
||||
if _cxxTypeNeedsMove(ipdltype):
|
||||
t.ref = 2
|
||||
return t
|
||||
return _cxxConstRefType(ipdltype, side)
|
||||
|
@ -631,6 +640,9 @@ info needed by later passes, along with a basic name for the decl."""
|
|||
self.name = name
|
||||
self.idnum = 0
|
||||
|
||||
def isCopyable(self):
|
||||
return not _cxxTypeNeedsMove(self.ipdltype)
|
||||
|
||||
def var(self):
|
||||
return ExprVar(self.name)
|
||||
|
||||
|
@ -4084,9 +4096,9 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
ifnotpromise ]
|
||||
if len(md.returns) > 1:
|
||||
resolvearg = ExprCall(ExprVar('MakeTuple'),
|
||||
args=[p.var() for p in md.returns])
|
||||
args=[ExprMove(p.var()) for p in md.returns])
|
||||
else:
|
||||
resolvearg = md.returns[0].var()
|
||||
resolvearg = ExprMove(md.returns[0].var())
|
||||
|
||||
resolvepromise = [ StmtExpr(ExprCall(ExprSelect(ExprVar('promise'), '->', 'Resolve'),
|
||||
args=[ resolvearg,
|
||||
|
@ -4287,13 +4299,13 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
args=[ self.replyvar ])),
|
||||
failifsendok ])
|
||||
if len(md.returns) > 1:
|
||||
resolvedecl = Decl(_tuple([p.bareType(self.side) for p in md.returns],
|
||||
resolvedecl = Decl(_tuple([p.moveType(self.side) for p in md.returns],
|
||||
const=1, ref=1),
|
||||
'aParam')
|
||||
destructexpr = ExprCall(ExprVar('Tie'),
|
||||
args=[ p.var() for p in md.returns ])
|
||||
else:
|
||||
resolvedecl = Decl(md.returns[0].bareType(self.side), 'aParam')
|
||||
resolvedecl = Decl(md.returns[0].moveType(self.side), 'aParam')
|
||||
destructexpr = md.returns[0].var()
|
||||
selfvar = ExprVar('self__')
|
||||
ifactorisdead = StmtIf(ExprNot(selfvar))
|
||||
|
@ -4312,7 +4324,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
init=ExprLiteral.TRUE) ]
|
||||
+ [ StmtDecl(Decl(p.bareType(self.side), p.var().name))
|
||||
for p in md.returns ]
|
||||
+ [ StmtExpr(ExprAssn(destructexpr, ExprVar('aParam'))),
|
||||
+ [ StmtExpr(ExprAssn(destructexpr, ExprMove(ExprVar('aParam')))),
|
||||
StmtDecl(Decl(Type('IPC::Message', ptr=1), self.replyvar.name),
|
||||
init=ExprCall(ExprVar(md.pqReplyCtorFunc()),
|
||||
args=[ routingId ])) ]
|
||||
|
|
|
@ -92,6 +92,9 @@
|
|||
#define NS_ADDONPATHSERVICE_CONTRACTID \
|
||||
"@mozilla.org/addon-path-service;1"
|
||||
|
||||
#define NS_WEBREQUESTSERVICE_CONTRACTID \
|
||||
"@mozilla.org/addons/webrequest-service;1"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define ALERT_NOTIFICATION_CID \
|
||||
|
@ -192,3 +195,7 @@
|
|||
|
||||
#define NS_ADDON_POLICY_SERVICE_CONTRACTID \
|
||||
"@mozilla.org/addons/policy-service;1"
|
||||
|
||||
// {5dd0c968-d74d-42c3-b930-36145f885c3b}
|
||||
#define NS_WEBREQUEST_SERVICE_CID \
|
||||
{ 0x5dd0c968, 0xd74d, 0x42c3, { 0xb9, 0x30, 0x36, 0x14, 0x5f, 0x88, 0x5c, 0x3b } }
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "mozilla/AddonManagerStartup.h"
|
||||
#include "mozilla/AddonPathService.h"
|
||||
#include "mozilla/ExtensionPolicyService.h"
|
||||
#include "mozilla/WebRequestService.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#include "NativeFileWatcherWin.h"
|
||||
|
@ -128,6 +129,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(AddonContentPolicy)
|
|||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonManagerStartup, AddonManagerStartup::GetInstance)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ExtensionPolicyService, ExtensionPolicyService::GetInstance)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WebRequestService, WebRequestService::GetInstance)
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebRequestListener)
|
||||
|
||||
|
@ -164,6 +166,7 @@ NS_DEFINE_NAMED_CID(NS_ADDONCONTENTPOLICY_CID);
|
|||
NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_ADDON_MANAGER_STARTUP_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_ADDON_POLICY_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_WEBREQUEST_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_WEBREQUESTLISTENER_CID);
|
||||
|
||||
|
@ -200,6 +203,7 @@ static const Module::CIDEntry kToolkitCIDs[] = {
|
|||
{ &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
|
||||
{ &kNS_ADDON_MANAGER_STARTUP_CID, false, nullptr, AddonManagerStartupConstructor },
|
||||
{ &kNS_ADDON_POLICY_SERVICE_CID, false, nullptr, ExtensionPolicyServiceConstructor },
|
||||
{ &kNS_WEBREQUEST_SERVICE_CID, false, nullptr, WebRequestServiceConstructor },
|
||||
{ &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor },
|
||||
{ &kNS_WEBREQUESTLISTENER_CID, false, nullptr, nsWebRequestListenerConstructor },
|
||||
{ nullptr }
|
||||
|
@ -239,6 +243,7 @@ static const Module::ContractIDEntry kToolkitContracts[] = {
|
|||
{ NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
|
||||
{ NS_ADDONMANAGERSTARTUP_CONTRACTID, &kNS_ADDON_MANAGER_STARTUP_CID },
|
||||
{ NS_ADDON_POLICY_SERVICE_CONTRACTID, &kNS_ADDON_POLICY_SERVICE_CID },
|
||||
{ NS_WEBREQUESTSERVICE_CONTRACTID, &kNS_WEBREQUEST_SERVICE_CID },
|
||||
{ NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID },
|
||||
{ NS_WEBREQUESTLISTENER_CONTRACTID, &kNS_WEBREQUESTLISTENER_CID },
|
||||
{ nullptr }
|
||||
|
|
|
@ -313,18 +313,12 @@ this.ExtensionTestCommon = class ExtensionTestCommon {
|
|||
}
|
||||
|
||||
/**
|
||||
* Properly serialize a script into eval-able code string.
|
||||
* Properly serialize a function into eval-able code string.
|
||||
*
|
||||
* @param {string|function|Array} script
|
||||
* @param {function} script
|
||||
* @returns {string}
|
||||
*/
|
||||
static serializeScript(script) {
|
||||
if (Array.isArray(script)) {
|
||||
return script.map(this.serializeScript).join(";");
|
||||
}
|
||||
if (typeof script !== "function") {
|
||||
return script;
|
||||
}
|
||||
static serializeFunction(script) {
|
||||
// Serialization of object methods doesn't include `function` anymore.
|
||||
const method = /^(async )?(\w+)\(/;
|
||||
|
||||
|
@ -333,7 +327,23 @@ this.ExtensionTestCommon = class ExtensionTestCommon {
|
|||
if (match && match[2] !== "function") {
|
||||
code = code.replace(method, "$1function $2(");
|
||||
}
|
||||
return `(${code})();`;
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly serialize a script into eval-able code string.
|
||||
*
|
||||
* @param {string|function|Array} script
|
||||
* @returns {string}
|
||||
*/
|
||||
static serializeScript(script) {
|
||||
if (Array.isArray(script)) {
|
||||
return Array.from(script, this.serializeScript, this).join(";");
|
||||
}
|
||||
if (typeof script !== "function") {
|
||||
return script;
|
||||
}
|
||||
return `(${this.serializeFunction(script)})();`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -77,6 +77,13 @@ extensions.registerModules({
|
|||
["test"],
|
||||
],
|
||||
},
|
||||
webRequest: {
|
||||
url: "chrome://extensions/content/ext-c-webRequest.js",
|
||||
scopes: ["addon_child"],
|
||||
paths: [
|
||||
["webRequest"],
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (AppConstants.MOZ_BUILD_APP === "browser") {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
var {
|
||||
ExtensionError,
|
||||
} = ExtensionCommon;
|
||||
|
||||
this.webRequest = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
webRequest: {
|
||||
filterResponseData(requestId) {
|
||||
if (AppConstants.RELEASE_OR_BETA) {
|
||||
throw new ExtensionError("filterResponseData() unsupported in release builds");
|
||||
}
|
||||
requestId = parseInt(requestId, 10);
|
||||
|
||||
return context.cloneScope.StreamFilter.create(
|
||||
requestId, context.extension.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -100,10 +100,12 @@ function WebRequestEventManager(context, eventName) {
|
|||
filter2.windowId = filter.windowId;
|
||||
}
|
||||
|
||||
let blockingAllowed = context.extension.hasPermission("webRequestBlocking");
|
||||
|
||||
let info2 = [];
|
||||
if (info) {
|
||||
for (let desc of info) {
|
||||
if (desc == "blocking" && !context.extension.hasPermission("webRequestBlocking")) {
|
||||
if (desc == "blocking" && !blockingAllowed) {
|
||||
Cu.reportError("Using webRequest.addListener with the blocking option " +
|
||||
"requires the 'webRequestBlocking' permission.");
|
||||
} else {
|
||||
|
@ -112,7 +114,15 @@ function WebRequestEventManager(context, eventName) {
|
|||
}
|
||||
}
|
||||
|
||||
WebRequest[eventName].addListener(listener, filter2, info2);
|
||||
let listenerDetails = {
|
||||
addonId: context.extension.id,
|
||||
blockingAllowed,
|
||||
tabParent: context.xulBrowser.frameLoader.tabParent,
|
||||
};
|
||||
|
||||
WebRequest[eventName].addListener(
|
||||
listener, filter2, info2,
|
||||
listenerDetails);
|
||||
return () => {
|
||||
WebRequest[eventName].removeListener(listener);
|
||||
};
|
||||
|
|
|
@ -41,3 +41,4 @@ toolkit.jar:
|
|||
content/extensions/ext-c-storage.js
|
||||
content/extensions/ext-c-test.js
|
||||
content/extensions/ext-c-toolkit.js
|
||||
content/extensions/ext-c-webRequest.js
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# 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/.
|
||||
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'WebExtensions: General')
|
||||
|
||||
|
|
|
@ -203,6 +203,23 @@
|
|||
"parameters": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "filterResponseData",
|
||||
"permissions": ["webRequestBlocking"],
|
||||
"type": "function",
|
||||
"description": "...",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requestId",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"returns": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "any"},
|
||||
"isInstanceOf": "StreamFilter"
|
||||
}
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
|
Двоичный файл не отображается.
|
@ -0,0 +1,2 @@
|
|||
Content-Type: text/html; charset=utf-8
|
||||
Content-Encoding: gzip
|
|
@ -49,7 +49,10 @@ support-files =
|
|||
file_ext_test_api_injection.js
|
||||
file_permission_xhr.html
|
||||
file_teardown_test.js
|
||||
lorem.html.gz
|
||||
lorem.html.gz^headers^
|
||||
return_headers.sjs
|
||||
slow_response.sjs
|
||||
webrequest_worker.js
|
||||
!/toolkit/components/passwordmgr/test/authenticate.sjs
|
||||
!/dom/tests/mochitest/geolocation/network_geolocation.sjs
|
||||
|
@ -127,6 +130,8 @@ skip-if = os == 'android'
|
|||
[test_ext_webrequest_basic.html]
|
||||
[test_ext_webrequest_filter.html]
|
||||
[test_ext_webrequest_frameId.html]
|
||||
[test_ext_webrequest_responseBody.html]
|
||||
skip-if = release_or_beta || os == 'android'
|
||||
[test_ext_webrequest_suspend.html]
|
||||
[test_ext_webrequest_upload.html]
|
||||
skip-if = os == 'android' # Currently fails in emulator tests
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */
|
||||
"use strict";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
const DELAY = AppConstants.DEBUG ? 2000 : 200;
|
||||
|
||||
let nsTimer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
|
||||
|
||||
let timer;
|
||||
function delay() {
|
||||
return new Promise(resolve => {
|
||||
timer = nsTimer(resolve, DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
});
|
||||
}
|
||||
|
||||
const PARTS = [
|
||||
`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>`,
|
||||
"Lorem ipsum dolor sit amet, <br>",
|
||||
"consectetur adipiscing elit, <br>",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <br>",
|
||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. <br>",
|
||||
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br>",
|
||||
"Excepteur sint occaecat cupidatat non proident, <br>",
|
||||
"sunt in culpa qui officia deserunt mollit anim id est laborum.<br>",
|
||||
`
|
||||
</body>
|
||||
</html>`,
|
||||
];
|
||||
|
||||
async function handleRequest(request, response) {
|
||||
response.processAsync();
|
||||
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
|
||||
await delay();
|
||||
|
||||
for (let part of PARTS) {
|
||||
response.write(`${part}\n`);
|
||||
await delay();
|
||||
}
|
||||
|
||||
response.finish();
|
||||
}
|
||||
|
|
@ -0,0 +1,461 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>WebRequest response body filter test</title>
|
||||
<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>
|
||||
"use strict";
|
||||
|
||||
const SEQUENTIAL = false;
|
||||
|
||||
const PARTS = [
|
||||
`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>`,
|
||||
"Lorem ipsum dolor sit amet, <br>",
|
||||
"consectetur adipiscing elit, <br>",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <br>",
|
||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. <br>",
|
||||
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <br>",
|
||||
"Excepteur sint occaecat cupidatat non proident, <br>",
|
||||
"sunt in culpa qui officia deserunt mollit anim id est laborum.<br>",
|
||||
`
|
||||
</body>
|
||||
</html>`,
|
||||
].map(part => `${part}\n`);
|
||||
|
||||
const TIMEOUT = AppConstants.DEBUG ? 2000 : 200;
|
||||
const TASKS = [
|
||||
{
|
||||
url: "slow_response.sjs",
|
||||
task(filter, resolve, num) {
|
||||
let decoder = new TextDecoder("utf-8");
|
||||
|
||||
browser.test.assertEq("uninitialized", filter.status,
|
||||
`(${num}): Got expected initial status`);
|
||||
|
||||
filter.onstart = event => {
|
||||
browser.test.assertEq("transferringdata", filter.status,
|
||||
`(${num}): Got expected onStart status`);
|
||||
};
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected onStop event while disconnected`);
|
||||
};
|
||||
|
||||
let n = 0;
|
||||
filter.ondata = async event => {
|
||||
let str = decoder.decode(event.data, {stream: true});
|
||||
|
||||
if (n < 3) {
|
||||
browser.test.assertEq(JSON.stringify(PARTS[n]),
|
||||
JSON.stringify(str),
|
||||
`(${num}): Got expected part`);
|
||||
}
|
||||
n++;
|
||||
|
||||
filter.write(event.data);
|
||||
|
||||
if (n == 3) {
|
||||
filter.suspend();
|
||||
|
||||
browser.test.assertEq("suspended", filter.status,
|
||||
`(${num}): Got expected suspended status`);
|
||||
|
||||
let fail = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected data event while suspended`);
|
||||
};
|
||||
filter.addEventListener("data", fail);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, TIMEOUT * 3));
|
||||
|
||||
browser.test.assertEq("suspended", filter.status,
|
||||
`(${num}): Got expected suspended status`);
|
||||
|
||||
filter.removeEventListener("data", fail);
|
||||
filter.resume();
|
||||
browser.test.assertEq("transferringdata", filter.status,
|
||||
`(${num}): Got expected resumed status`);
|
||||
} else if (n > 4) {
|
||||
filter.disconnect();
|
||||
|
||||
filter.addEventListener("data", event => {
|
||||
browser.test.fail(`(${num}): Got unexpected data event while disconnected`);
|
||||
});
|
||||
|
||||
browser.test.assertEq("disconnected", filter.status,
|
||||
`(${num}): Got expected disconnected status`);
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "slow_response.sjs",
|
||||
task(filter, resolve, num) {
|
||||
let decoder = new TextDecoder("utf-8");
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected onStop event while disconnected`);
|
||||
};
|
||||
|
||||
let n = 0;
|
||||
filter.ondata = async event => {
|
||||
let str = decoder.decode(event.data, {stream: true});
|
||||
|
||||
if (n < 3) {
|
||||
browser.test.assertEq(JSON.stringify(PARTS[n]),
|
||||
JSON.stringify(str),
|
||||
`(${num}): Got expected part`);
|
||||
}
|
||||
n++;
|
||||
|
||||
filter.write(event.data);
|
||||
|
||||
if (n == 3) {
|
||||
filter.suspend();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, TIMEOUT * 3));
|
||||
|
||||
filter.disconnect();
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "slow_response.sjs",
|
||||
task(filter, resolve, num) {
|
||||
let encoder = new TextEncoder("utf-8");
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected onStop event while disconnected`);
|
||||
};
|
||||
|
||||
let n = 0;
|
||||
filter.ondata = async event => {
|
||||
n++;
|
||||
|
||||
filter.write(event.data);
|
||||
|
||||
function checkState(state) {
|
||||
browser.test.assertEq(state, filter.status, `(${num}): Got expected status`);
|
||||
}
|
||||
if (n == 3) {
|
||||
filter.resume();
|
||||
checkState("transferringdata");
|
||||
filter.suspend();
|
||||
checkState("suspended");
|
||||
filter.suspend();
|
||||
checkState("suspended");
|
||||
filter.resume();
|
||||
checkState("transferringdata");
|
||||
filter.suspend();
|
||||
checkState("suspended");
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, TIMEOUT * 3));
|
||||
|
||||
checkState("suspended");
|
||||
filter.disconnect();
|
||||
checkState("disconnected");
|
||||
|
||||
for (let method of ["suspend", "resume", "close"]) {
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter[method]();
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): ${method}() should throw while disconnected`);
|
||||
}
|
||||
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter.write(encoder.encode("Foo bar"));
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): write() should throw while disconnected`);
|
||||
|
||||
filter.disconnect();
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
{
|
||||
url: "slow_response.sjs",
|
||||
task(filter, resolve, num) {
|
||||
let encoder = new TextEncoder("utf-8");
|
||||
let decoder = new TextDecoder("utf-8");
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected onStop event while closed`);
|
||||
};
|
||||
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter.write(encoder.encode("Foo bar"));
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): write() should throw prior to connection`);
|
||||
|
||||
let n = 0;
|
||||
filter.ondata = async event => {
|
||||
n++;
|
||||
|
||||
filter.write(event.data);
|
||||
|
||||
browser.test.log(`(${num}): Got part ${n}: ${JSON.stringify(decoder.decode(event.data))}`);
|
||||
|
||||
function checkState(state) {
|
||||
browser.test.assertEq(state, filter.status, `(${num}): Got expected status`);
|
||||
}
|
||||
if (n == 3) {
|
||||
filter.close();
|
||||
|
||||
checkState("closed");
|
||||
|
||||
for (let method of ["suspend", "resume", "disconnect"]) {
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter[method]();
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): ${method}() should throw while closed`);
|
||||
}
|
||||
|
||||
browser.test.assertThrows(
|
||||
() => {
|
||||
filter.write(encoder.encode("Foo bar"));
|
||||
},
|
||||
/.*/,
|
||||
`(${num}): write() should throw while closed`);
|
||||
|
||||
filter.close();
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.slice(0, 3).join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
url: "lorem.html.gz",
|
||||
task(filter, resolve, num) {
|
||||
let response = "";
|
||||
let decoder = new TextDecoder("utf-8");
|
||||
|
||||
filter.onstart = event => {
|
||||
browser.test.log(`(${num}): Request start`);
|
||||
};
|
||||
|
||||
filter.onstop = event => {
|
||||
browser.test.assertEq("finishedtransferringdata", filter.status,
|
||||
`(${num}): Got expected onStop status`);
|
||||
|
||||
filter.close();
|
||||
browser.test.assertEq("closed", filter.status,
|
||||
`Got expected closed status`);
|
||||
|
||||
|
||||
browser.test.assertEq(JSON.stringify(PARTS.join("")),
|
||||
JSON.stringify(response),
|
||||
`(${num}): Got expected response`);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
filter.ondata = event => {
|
||||
let str = decoder.decode(event.data, {stream: true});
|
||||
response += str;
|
||||
|
||||
filter.write(event.data);
|
||||
};
|
||||
|
||||
filter.onerror = event => {
|
||||
browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
|
||||
};
|
||||
},
|
||||
verify(response) {
|
||||
is(response, PARTS.join(""), "Got expected final HTML");
|
||||
},
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
function serializeTest(test, num) {
|
||||
/* globals ExtensionTestCommon */
|
||||
|
||||
let url = `${test.url}?test_num=${num}`;
|
||||
let task = ExtensionTestCommon.serializeFunction(test.task);
|
||||
|
||||
return `{url: ${JSON.stringify(url)}, task: ${task}}`;
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
function background(TASKS) {
|
||||
async function runTest(test, num, details) {
|
||||
browser.test.log(`Running test #${num}: ${details.url}`);
|
||||
|
||||
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||
|
||||
try {
|
||||
await new Promise(resolve => {
|
||||
test.task(filter, resolve, num, details);
|
||||
});
|
||||
} catch (e) {
|
||||
browser.test.fail(`Task #${num} threw an unexpected exception: ${e} :: ${e.stack}`);
|
||||
}
|
||||
|
||||
browser.test.log(`Finished test #${num}: ${details.url}`);
|
||||
browser.test.sendMessage(`finished-${num}`);
|
||||
}
|
||||
|
||||
browser.webRequest.onBeforeRequest.addListener(
|
||||
details => {
|
||||
for (let [num, test] of TASKS.entries()) {
|
||||
if (details.url.endsWith(test.url)) {
|
||||
runTest(test, num, details);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
urls: ["http://mochi.test/*?test_num=*"],
|
||||
},
|
||||
["blocking"]);
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `
|
||||
const PARTS = ${JSON.stringify(PARTS)};
|
||||
const TIMEOUT = ${TIMEOUT};
|
||||
|
||||
(${background})([${TASKS.map(serializeTest)}])
|
||||
`,
|
||||
|
||||
manifest: {
|
||||
permissions: [
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"http://mochi.test/",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
async function runTest(test, num) {
|
||||
let url = `${test.url}?test_num=${num}`;
|
||||
|
||||
let resp = await fetch(url);
|
||||
let body = await resp.text();
|
||||
|
||||
await extension.awaitMessage(`finished-${num}`);
|
||||
|
||||
info(`Verifying test #${num}: ${url}`);
|
||||
await test.verify(body);
|
||||
}
|
||||
|
||||
if (SEQUENTIAL) {
|
||||
for (let [num, test] of TASKS.entries()) {
|
||||
await runTest(test, num);
|
||||
}
|
||||
} else {
|
||||
await Promise.all(TASKS.map(runTest));
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_permissions() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.test.assertEq(
|
||||
undefined, browser.webRequest.filterResponseData,
|
||||
"filterResponseData is undefined without blocking permissions");
|
||||
},
|
||||
|
||||
manifest: {
|
||||
permissions: [
|
||||
"webRequest",
|
||||
"http://mochi.test/",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_invalidId() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
async background() {
|
||||
let filter = browser.webRequest.filterResponseData("34159628");
|
||||
|
||||
await new Promise(resolve => { filter.onerror = resolve; });
|
||||
|
||||
browser.test.assertEq("Invalid request ID",
|
||||
filter.error,
|
||||
"Got expected error");
|
||||
|
||||
browser.test.notifyPass("invalid-request-id");
|
||||
},
|
||||
|
||||
manifest: {
|
||||
permissions: [
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"http://mochi.test/",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("invalid-request-id");
|
||||
await extension.unload();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
/* 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 protocol PBackground;
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
protocol PStreamFilter
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
parent:
|
||||
async Write(uint8_t[] data);
|
||||
|
||||
async FlushedData();
|
||||
|
||||
async Suspend();
|
||||
async Resume();
|
||||
async Close();
|
||||
async Disconnect();
|
||||
|
||||
child:
|
||||
async Initialized(bool aSuccess);
|
||||
async Resumed();
|
||||
async Suspended();
|
||||
async Closed();
|
||||
|
||||
async FlushData();
|
||||
|
||||
async StartRequest();
|
||||
async Data(uint8_t[] data);
|
||||
async StopRequest(nsresult aStatus);
|
||||
|
||||
async __delete__();
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "StreamFilter.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/SystemGroup.h"
|
||||
#include "mozilla/extensions/StreamFilterChild.h"
|
||||
#include "mozilla/extensions/StreamFilterEvents.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsLiteralString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
using namespace JS;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
using mozilla::ipc::BackgroundChild;
|
||||
using mozilla::ipc::PBackgroundChild;
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
/*****************************************************************************
|
||||
* Initialization
|
||||
*****************************************************************************/
|
||||
|
||||
StreamFilter::StreamFilter(nsIGlobalObject* aParent,
|
||||
uint64_t aRequestId,
|
||||
const nsAString& aAddonId)
|
||||
: mParent(aParent)
|
||||
, mChannelId(aRequestId)
|
||||
, mAddonId(NS_Atomize(aAddonId))
|
||||
{
|
||||
MOZ_ASSERT(aParent);
|
||||
|
||||
ConnectToPBackground();
|
||||
};
|
||||
|
||||
StreamFilter::~StreamFilter()
|
||||
{
|
||||
ForgetActor();
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::ForgetActor()
|
||||
{
|
||||
if (mActor) {
|
||||
mActor->Cleanup();
|
||||
mActor->SetStreamFilter(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* static */ already_AddRefed<StreamFilter>
|
||||
StreamFilter::Create(GlobalObject& aGlobal, uint64_t aRequestId, const nsAString& aAddonId)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
MOZ_ASSERT(global);
|
||||
|
||||
RefPtr<StreamFilter> filter = new StreamFilter(global, aRequestId, aAddonId);
|
||||
return filter.forget();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Actor allocation
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilter::ConnectToPBackground()
|
||||
{
|
||||
PBackgroundChild* background = BackgroundChild::GetForCurrentThread();
|
||||
if (background) {
|
||||
ActorCreated(background);
|
||||
} else {
|
||||
bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
|
||||
MOZ_RELEASE_ASSERT(ok);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::ActorFailed()
|
||||
{
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor");
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::ActorCreated(PBackgroundChild* aBackground)
|
||||
{
|
||||
MOZ_ASSERT(aBackground);
|
||||
MOZ_ASSERT(!mActor);
|
||||
|
||||
nsAutoString addonId;
|
||||
mAddonId->ToString(addonId);
|
||||
|
||||
PStreamFilterChild* actor = aBackground->SendPStreamFilterConstructor(mChannelId, addonId);
|
||||
MOZ_ASSERT(actor);
|
||||
|
||||
mActor = static_cast<StreamFilterChild*>(actor);
|
||||
mActor->SetStreamFilter(this);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Binding methods
|
||||
*****************************************************************************/
|
||||
|
||||
template <typename T>
|
||||
static inline bool
|
||||
ReadTypedArrayData(nsTArray<uint8_t>& aData, const T& aArray, ErrorResult& aRv)
|
||||
{
|
||||
aArray.ComputeLengthAndData();
|
||||
if (!aData.SetLength(aArray.Length(), fallible)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return false;
|
||||
}
|
||||
memcpy(aData.Elements(), aArray.Data(), aArray.Length());
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::Write(const ArrayBufferOrUint8Array& aData, ErrorResult& aRv)
|
||||
{
|
||||
if (!mActor) {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> data;
|
||||
|
||||
bool ok;
|
||||
if (aData.IsArrayBuffer()) {
|
||||
ok = ReadTypedArrayData(data, aData.GetAsArrayBuffer(), aRv);
|
||||
} else if (aData.IsUint8Array()) {
|
||||
ok = ReadTypedArrayData(data, aData.GetAsUint8Array(), aRv);
|
||||
} else {
|
||||
MOZ_ASSERT_UNREACHABLE("Argument should be ArrayBuffer or Uint8Array");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
mActor->Write(Move(data), aRv);
|
||||
}
|
||||
}
|
||||
|
||||
StreamFilterStatus
|
||||
StreamFilter::Status() const
|
||||
{
|
||||
if (!mActor) {
|
||||
return StreamFilterStatus::Uninitialized;
|
||||
}
|
||||
return mActor->Status();
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::Suspend(ErrorResult& aRv)
|
||||
{
|
||||
if (mActor) {
|
||||
mActor->Suspend(aRv);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::Resume(ErrorResult& aRv)
|
||||
{
|
||||
if (mActor) {
|
||||
mActor->Resume(aRv);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::Disconnect(ErrorResult& aRv)
|
||||
{
|
||||
if (mActor) {
|
||||
mActor->Disconnect(aRv);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::Close(ErrorResult& aRv)
|
||||
{
|
||||
if (mActor) {
|
||||
mActor->Close(aRv);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Event emitters
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilter::FireEvent(const nsAString& aType)
|
||||
{
|
||||
EventInit init;
|
||||
init.mBubbles = false;
|
||||
init.mCancelable = false;
|
||||
|
||||
RefPtr<Event> event = Event::Constructor(this, aType, init);
|
||||
event->SetTrusted(true);
|
||||
|
||||
bool defaultPrevented;
|
||||
DispatchEvent(event, &defaultPrevented);
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::FireDataEvent(const nsTArray<uint8_t>& aData)
|
||||
{
|
||||
AutoEntryScript aes(mParent, "StreamFilter data event");
|
||||
JSContext* cx = aes.cx();
|
||||
|
||||
RootedDictionary<StreamFilterDataEventInit> init(cx);
|
||||
init.mBubbles = false;
|
||||
init.mCancelable = false;
|
||||
|
||||
auto buffer = ArrayBuffer::Create(cx, aData.Length(), aData.Elements());
|
||||
if (!buffer) {
|
||||
// TODO: There is no way to recover from this. This chunk of data is lost.
|
||||
FireErrorEvent(NS_LITERAL_STRING("Out of memory"));
|
||||
return;
|
||||
}
|
||||
|
||||
init.mData.Init(buffer);
|
||||
|
||||
RefPtr<StreamFilterDataEvent> event =
|
||||
StreamFilterDataEvent::Constructor(this, NS_LITERAL_STRING("data"), init);
|
||||
event->SetTrusted(true);
|
||||
|
||||
bool defaultPrevented;
|
||||
DispatchEvent(event, &defaultPrevented);
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilter::FireErrorEvent(const nsAString& aError)
|
||||
{
|
||||
MOZ_ASSERT(mError.IsEmpty());
|
||||
|
||||
mError = aError;
|
||||
FireEvent(NS_LITERAL_STRING("error"));
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Glue
|
||||
*****************************************************************************/
|
||||
|
||||
/* static */ bool
|
||||
StreamFilter::IsAllowedInContext(JSContext* aCx, JSObject* /* unused */)
|
||||
{
|
||||
return nsContentUtils::CallerHasPermission(aCx, NS_LITERAL_STRING("webRequestBlocking"));
|
||||
}
|
||||
|
||||
JSObject*
|
||||
StreamFilter::WrapObject(JSContext* aCx, HandleObject aGivenProto)
|
||||
{
|
||||
return StreamFilterBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(StreamFilter)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StreamFilter)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,98 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
#ifndef mozilla_extensions_StreamFilter_h
|
||||
#define mozilla_extensions_StreamFilter_h
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/StreamFilterBinding.h"
|
||||
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIAtom.h"
|
||||
#include "nsIIPCBackgroundChildCreateCallback.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
class StreamFilterChild;
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
class StreamFilter : public DOMEventTargetHelper
|
||||
, public nsIIPCBackgroundChildCreateCallback
|
||||
{
|
||||
friend class StreamFilterChild;
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(StreamFilter, DOMEventTargetHelper)
|
||||
|
||||
static already_AddRefed<StreamFilter>
|
||||
Create(GlobalObject& global,
|
||||
uint64_t aRequestId,
|
||||
const nsAString& aAddonId);
|
||||
|
||||
explicit StreamFilter(nsIGlobalObject* aParent,
|
||||
uint64_t aRequestId,
|
||||
const nsAString& aAddonId);
|
||||
|
||||
IMPL_EVENT_HANDLER(start);
|
||||
IMPL_EVENT_HANDLER(stop);
|
||||
IMPL_EVENT_HANDLER(data);
|
||||
IMPL_EVENT_HANDLER(error);
|
||||
|
||||
void Write(const ArrayBufferOrUint8Array& aData,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void GetError(nsAString& aError)
|
||||
{
|
||||
aError = mError;
|
||||
}
|
||||
|
||||
StreamFilterStatus Status() const;
|
||||
void Suspend(ErrorResult& aRv);
|
||||
void Resume(ErrorResult& aRv);
|
||||
void Disconnect(ErrorResult& aRv);
|
||||
void Close(ErrorResult& aRv);
|
||||
|
||||
nsISupports* GetParentObject() const { return mParent; }
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
static bool
|
||||
IsAllowedInContext(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
protected:
|
||||
virtual ~StreamFilter();
|
||||
|
||||
void FireEvent(const nsAString& aType);
|
||||
|
||||
void FireDataEvent(const nsTArray<uint8_t>& aData);
|
||||
|
||||
void FireErrorEvent(const nsAString& aError);
|
||||
|
||||
private:
|
||||
void
|
||||
ConnectToPBackground();
|
||||
|
||||
void ForgetActor();
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mParent;
|
||||
RefPtr<StreamFilterChild> mActor;
|
||||
|
||||
nsString mError;
|
||||
|
||||
const uint64_t mChannelId;
|
||||
const nsCOMPtr<nsIAtom> mAddonId;
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_extensions_StreamFilter_h
|
|
@ -0,0 +1,40 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_extensions_StreamFilterBase_h
|
||||
#define mozilla_extensions_StreamFilterBase_h
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
class StreamFilterBase
|
||||
{
|
||||
public:
|
||||
typedef nsTArray<uint8_t> Data;
|
||||
|
||||
protected:
|
||||
class BufferedData : public LinkedListElement<BufferedData> {
|
||||
public:
|
||||
explicit BufferedData(Data&& aData) : mData(Move(aData)) {}
|
||||
|
||||
Data mData;
|
||||
};
|
||||
|
||||
LinkedList<BufferedData> mBufferedData;
|
||||
|
||||
inline void
|
||||
BufferData(Data&& aData) {
|
||||
mBufferedData.insertBack(new BufferedData(Move(aData)));
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_extensions_StreamFilterBase_h
|
|
@ -0,0 +1,518 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "StreamFilterChild.h"
|
||||
#include "StreamFilter.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
using mozilla::dom::StreamFilterStatus;
|
||||
using mozilla::ipc::IPCResult;
|
||||
|
||||
/*****************************************************************************
|
||||
* Initialization and cleanup
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::Cleanup()
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Closing:
|
||||
case State::Closed:
|
||||
case State::Error:
|
||||
case State::Disconnecting:
|
||||
case State::Disconnected:
|
||||
break;
|
||||
|
||||
default:
|
||||
ErrorResult rv;
|
||||
Disconnect(rv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* State change methods
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::Suspend(ErrorResult& aRv)
|
||||
{
|
||||
switch (mState) {
|
||||
case State::TransferringData:
|
||||
mState = State::Suspending;
|
||||
mNextState = State::Suspended;
|
||||
|
||||
SendSuspend();
|
||||
break;
|
||||
|
||||
case State::Suspending:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::Resuming:
|
||||
mNextState = State::Suspended;
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::TransferringData:
|
||||
case State::Suspending:
|
||||
mNextState = State::Suspending;
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Suspended:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterChild::Resume(ErrorResult& aRv)
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Suspended:
|
||||
mState = State::Resuming;
|
||||
mNextState = State::TransferringData;
|
||||
|
||||
SendResume();
|
||||
break;
|
||||
|
||||
case State::Suspending:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::Resuming:
|
||||
mNextState = State::Resuming;
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Resuming:
|
||||
case State::TransferringData:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
FlushBufferedData();
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterChild::Disconnect(ErrorResult& aRv)
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Suspended:
|
||||
case State::TransferringData:
|
||||
case State::FinishedTransferringData:
|
||||
mState = State::Disconnecting;
|
||||
mNextState = State::Disconnected;
|
||||
|
||||
SendDisconnect();
|
||||
break;
|
||||
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::Resuming:
|
||||
case State::Disconnecting:
|
||||
mNextState = State::Disconnecting;
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Disconnecting:
|
||||
case State::Disconnected:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterChild::Close(ErrorResult& aRv)
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Suspended:
|
||||
case State::TransferringData:
|
||||
case State::FinishedTransferringData:
|
||||
mState = State::Closing;
|
||||
mNextState = State::Closed;
|
||||
|
||||
SendClose();
|
||||
break;
|
||||
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
mNextState = State::Closing;
|
||||
break;
|
||||
|
||||
case State::Closing:
|
||||
MOZ_DIAGNOSTIC_ASSERT(mNextState == State::Closed);
|
||||
break;
|
||||
|
||||
case State::Closed:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
mBufferedData.clear();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Internal state management
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::SetNextState()
|
||||
{
|
||||
mState = mNextState;
|
||||
|
||||
switch (mNextState) {
|
||||
case State::Suspending:
|
||||
mNextState = State::Suspended;
|
||||
SendSuspend();
|
||||
break;
|
||||
|
||||
case State::Resuming:
|
||||
mNextState = State::TransferringData;
|
||||
SendResume();
|
||||
break;
|
||||
|
||||
case State::Closing:
|
||||
mNextState = State::Closed;
|
||||
SendClose();
|
||||
break;
|
||||
|
||||
case State::Disconnecting:
|
||||
mNextState = State::Disconnected;
|
||||
SendDisconnect();
|
||||
break;
|
||||
|
||||
case State::FinishedTransferringData:
|
||||
if (mStreamFilter) {
|
||||
mStreamFilter->FireEvent(NS_LITERAL_STRING("stop"));
|
||||
// We don't need access to the stream filter after this point, so break our
|
||||
// reference cycle, so that it can be collected if we're the last reference.
|
||||
mStreamFilter = nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::TransferringData:
|
||||
FlushBufferedData();
|
||||
break;
|
||||
|
||||
case State::Closed:
|
||||
case State::Disconnected:
|
||||
case State::Error:
|
||||
mStreamFilter = nullptr;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterChild::MaybeStopRequest()
|
||||
{
|
||||
if (!mReceivedOnStop || !mBufferedData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mState) {
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
mNextState = State::FinishedTransferringData;
|
||||
return;
|
||||
|
||||
default:
|
||||
mState = State::FinishedTransferringData;
|
||||
if (mStreamFilter) {
|
||||
mStreamFilter->FireEvent(NS_LITERAL_STRING("stop"));
|
||||
// We don't need access to the stream filter after this point, so break our
|
||||
// reference cycle, so that it can be collected if we're the last reference.
|
||||
mStreamFilter = nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* State change acknowledgment callbacks
|
||||
*****************************************************************************/
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvInitialized(const bool& aSuccess)
|
||||
{
|
||||
MOZ_ASSERT(mState == State::Uninitialized);
|
||||
|
||||
if (aSuccess) {
|
||||
mState = State::Initialized;
|
||||
} else {
|
||||
mState = State::Error;
|
||||
if (mStreamFilter) {
|
||||
mStreamFilter->FireErrorEvent(NS_LITERAL_STRING("Invalid request ID"));
|
||||
mStreamFilter = nullptr;
|
||||
}
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvClosed() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState == State::Closing);
|
||||
|
||||
SetNextState();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvSuspended() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState == State::Suspending);
|
||||
|
||||
SetNextState();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvResumed() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState == State::Resuming);
|
||||
|
||||
SetNextState();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvFlushData() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState == State::Disconnecting);
|
||||
|
||||
SendFlushedData();
|
||||
SetNextState();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Other binding methods
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::Write(Data&& aData, ErrorResult& aRv)
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Suspending:
|
||||
case State::Resuming:
|
||||
switch (mNextState) {
|
||||
case State::Suspended:
|
||||
case State::TransferringData:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Suspended:
|
||||
case State::TransferringData:
|
||||
case State::FinishedTransferringData:
|
||||
break;
|
||||
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
SendWrite(Move(aData));
|
||||
}
|
||||
|
||||
StreamFilterStatus
|
||||
StreamFilterChild::Status() const
|
||||
{
|
||||
switch (mState) {
|
||||
case State::Uninitialized:
|
||||
case State::Initialized:
|
||||
return StreamFilterStatus::Uninitialized;
|
||||
|
||||
case State::TransferringData:
|
||||
return StreamFilterStatus::Transferringdata;
|
||||
|
||||
case State::Suspended:
|
||||
return StreamFilterStatus::Suspended;
|
||||
|
||||
case State::FinishedTransferringData:
|
||||
return StreamFilterStatus::Finishedtransferringdata;
|
||||
|
||||
case State::Resuming:
|
||||
case State::Suspending:
|
||||
switch (mNextState) {
|
||||
case State::TransferringData:
|
||||
case State::Resuming:
|
||||
return StreamFilterStatus::Transferringdata;
|
||||
|
||||
case State::Suspended:
|
||||
case State::Suspending:
|
||||
return StreamFilterStatus::Suspended;
|
||||
|
||||
case State::Closing:
|
||||
return StreamFilterStatus::Closed;
|
||||
|
||||
case State::Disconnecting:
|
||||
return StreamFilterStatus::Disconnected;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected next state");
|
||||
return StreamFilterStatus::Suspended;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Closing:
|
||||
case State::Closed:
|
||||
return StreamFilterStatus::Closed;
|
||||
|
||||
case State::Disconnecting:
|
||||
case State::Disconnected:
|
||||
return StreamFilterStatus::Disconnected;
|
||||
|
||||
case State::Error:
|
||||
return StreamFilterStatus::Failed;
|
||||
};
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Not reached");
|
||||
return StreamFilterStatus::Failed;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Request state notifications
|
||||
*****************************************************************************/
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvStartRequest()
|
||||
{
|
||||
MOZ_ASSERT(mState == State::Initialized);
|
||||
|
||||
mState = State::TransferringData;
|
||||
|
||||
if (mStreamFilter) {
|
||||
mStreamFilter->FireEvent(NS_LITERAL_STRING("start"));
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvStopRequest(const nsresult& aStatus)
|
||||
{
|
||||
mReceivedOnStop = true;
|
||||
MaybeStopRequest();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Incoming request data handling
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::EmitData(const Data& aData)
|
||||
{
|
||||
MOZ_ASSERT(CanFlushData());
|
||||
if (mStreamFilter) {
|
||||
mStreamFilter->FireDataEvent(aData);
|
||||
}
|
||||
|
||||
MaybeStopRequest();
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterChild::FlushBufferedData()
|
||||
{
|
||||
while (!mBufferedData.isEmpty() && CanFlushData()) {
|
||||
UniquePtr<BufferedData> data(mBufferedData.popFirst());
|
||||
|
||||
EmitData(data->mData);
|
||||
}
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterChild::RecvData(Data&& aData)
|
||||
{
|
||||
MOZ_ASSERT(!mReceivedOnStop);
|
||||
|
||||
switch (mState) {
|
||||
case State::TransferringData:
|
||||
case State::Resuming:
|
||||
EmitData(aData);
|
||||
break;
|
||||
|
||||
case State::FinishedTransferringData:
|
||||
MOZ_ASSERT_UNREACHABLE("Received data in unexpected state");
|
||||
EmitData(aData);
|
||||
break;
|
||||
|
||||
case State::Suspending:
|
||||
case State::Suspended:
|
||||
BufferData(Move(aData));
|
||||
break;
|
||||
|
||||
case State::Disconnecting:
|
||||
SendWrite(Move(aData));
|
||||
break;
|
||||
|
||||
case State::Closing:
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Received data in unexpected state");
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Glue
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
mStreamFilter = nullptr;
|
||||
}
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,146 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_extensions_StreamFilterChild_h
|
||||
#define mozilla_extensions_StreamFilterChild_h
|
||||
|
||||
#include "StreamFilterBase.h"
|
||||
#include "mozilla/extensions/PStreamFilterChild.h"
|
||||
#include "mozilla/extensions/StreamFilter.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/dom/StreamFilterBinding.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
using mozilla::dom::StreamFilterStatus;
|
||||
using mozilla::ipc::IPCResult;
|
||||
|
||||
class StreamFilter;
|
||||
|
||||
class StreamFilterChild final : public PStreamFilterChild
|
||||
, public StreamFilterBase
|
||||
{
|
||||
friend class StreamFilter;
|
||||
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(StreamFilterChild)
|
||||
|
||||
StreamFilterChild()
|
||||
: mState(State::Uninitialized)
|
||||
, mReceivedOnStop(false)
|
||||
{}
|
||||
|
||||
enum class State {
|
||||
// Uninitialized, waiting for constructor response from parent.
|
||||
Uninitialized,
|
||||
// Initialized, but channel has not begun transferring data.
|
||||
Initialized,
|
||||
// The stream's OnStartRequest event has been dispatched, and the channel is
|
||||
// transferring data.
|
||||
TransferringData,
|
||||
// The channel's OnStopRequest event has been dispatched, and the channel is
|
||||
// no longer transferring data. Data may still be written to the output
|
||||
// stream listener.
|
||||
FinishedTransferringData,
|
||||
// The channel is being suspended, and we're waiting for confirmation of
|
||||
// suspension from the parent.
|
||||
Suspending,
|
||||
// The channel has been suspended in the parent. Data may still be written
|
||||
// to the output stream listener in this state.
|
||||
Suspended,
|
||||
// The channel is suspended. Resume has been called, and we are waiting for
|
||||
// confirmation of resumption from the parent.
|
||||
Resuming,
|
||||
// The close() method has been called, and no further output may be written.
|
||||
// We are waiting for confirmation from the parent.
|
||||
Closing,
|
||||
// The close() method has been called, and we have been disconnected from
|
||||
// our parent.
|
||||
Closed,
|
||||
// The channel is being disconnected from the parent, and all further events
|
||||
// and data will pass unfiltered. Data received by the child in this state
|
||||
// will be automatically written ot the output stream listener. No data may
|
||||
// be explicitly written.
|
||||
Disconnecting,
|
||||
// The channel has been disconnected from the parent, and all further data
|
||||
// and events will be transparently passed to the output stream listener
|
||||
// without passing through the child.
|
||||
Disconnected,
|
||||
// An error has occurred and the child is disconnected from the parent.
|
||||
Error,
|
||||
};
|
||||
|
||||
void Suspend(ErrorResult& aRv);
|
||||
void Resume(ErrorResult& aRv);
|
||||
void Disconnect(ErrorResult& aRv);
|
||||
void Close(ErrorResult& aRv);
|
||||
void Cleanup();
|
||||
|
||||
void Write(Data&& aData, ErrorResult& aRv);
|
||||
|
||||
State GetState() const
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
StreamFilterStatus Status() const;
|
||||
|
||||
protected:
|
||||
virtual IPCResult RecvInitialized(const bool& aSuccess) override;
|
||||
|
||||
virtual IPCResult RecvStartRequest() override;
|
||||
virtual IPCResult RecvData(Data&& data) override;
|
||||
virtual IPCResult RecvStopRequest(const nsresult& aStatus) override;
|
||||
|
||||
virtual IPCResult RecvClosed() override;
|
||||
virtual IPCResult RecvSuspended() override;
|
||||
virtual IPCResult RecvResumed() override;
|
||||
virtual IPCResult RecvFlushData() override;
|
||||
|
||||
virtual IPCResult Recv__delete__() override { return IPC_OK(); }
|
||||
|
||||
void
|
||||
SetStreamFilter(StreamFilter* aStreamFilter)
|
||||
{
|
||||
mStreamFilter = aStreamFilter;
|
||||
}
|
||||
|
||||
private:
|
||||
~StreamFilterChild() {}
|
||||
|
||||
void SetNextState();
|
||||
|
||||
void MaybeStopRequest();
|
||||
|
||||
void EmitData(const Data& aData);
|
||||
|
||||
bool
|
||||
CanFlushData()
|
||||
{
|
||||
return (mState == State::TransferringData ||
|
||||
mState == State::Resuming);
|
||||
}
|
||||
|
||||
void FlushBufferedData();
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
|
||||
State mState;
|
||||
State mNextState;
|
||||
bool mReceivedOnStop;
|
||||
|
||||
RefPtr<StreamFilter> mStreamFilter;
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_extensions_StreamFilterChild_h
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 "mozilla/extensions/StreamFilterEvents.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(StreamFilterDataEvent)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(StreamFilterDataEvent, Event)
|
||||
NS_IMPL_RELEASE_INHERITED(StreamFilterDataEvent, Event)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StreamFilterDataEvent, Event)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StreamFilterDataEvent, Event)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StreamFilterDataEvent, Event)
|
||||
tmp->mData = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StreamFilterDataEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(Event)
|
||||
|
||||
|
||||
/* static */ already_AddRefed<StreamFilterDataEvent>
|
||||
StreamFilterDataEvent::Constructor(EventTarget* aEventTarget,
|
||||
const nsAString& aType,
|
||||
const StreamFilterDataEventInit& aParam)
|
||||
{
|
||||
RefPtr<StreamFilterDataEvent> event = new StreamFilterDataEvent(aEventTarget);
|
||||
|
||||
bool trusted = event->Init(aEventTarget);
|
||||
event->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
|
||||
event->SetTrusted(trusted);
|
||||
event->SetComposed(aParam.mComposed);
|
||||
|
||||
event->SetData(aParam.mData);
|
||||
|
||||
return event.forget();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
StreamFilterDataEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return StreamFilterDataEventBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
#ifndef mozilla_extensions_StreamFilterEvents_h
|
||||
#define mozilla_extensions_StreamFilterEvents_h
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/StreamFilterDataEventBinding.h"
|
||||
#include "mozilla/extensions/StreamFilter.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
using namespace JS;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
class StreamFilterDataEvent : public Event
|
||||
{
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(StreamFilterDataEvent, Event)
|
||||
|
||||
explicit StreamFilterDataEvent(EventTarget* aEventTarget)
|
||||
: Event(aEventTarget, nullptr, nullptr)
|
||||
{
|
||||
mozilla::HoldJSObjects(this);
|
||||
}
|
||||
|
||||
static already_AddRefed<StreamFilterDataEvent>
|
||||
Constructor(EventTarget* aEventTarget,
|
||||
const nsAString& aType,
|
||||
const StreamFilterDataEventInit& aParam);
|
||||
|
||||
static already_AddRefed<StreamFilterDataEvent>
|
||||
Constructor(GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const StreamFilterDataEventInit& aParam,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
return Constructor(target, aType, aParam);
|
||||
}
|
||||
|
||||
void GetData(JSContext* aCx, JS::MutableHandleObject aResult)
|
||||
{
|
||||
aResult.set(mData);
|
||||
}
|
||||
|
||||
virtual JSObject* WrapObjectInternal(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
protected:
|
||||
virtual ~StreamFilterDataEvent()
|
||||
{
|
||||
mozilla::DropJSObjects(this);
|
||||
}
|
||||
|
||||
private:
|
||||
JS::Heap<JSObject*> mData;
|
||||
|
||||
void
|
||||
SetData(const ArrayBuffer& aData)
|
||||
{
|
||||
mData = aData.Obj();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_extensions_StreamFilterEvents_h
|
|
@ -0,0 +1,455 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "StreamFilterParent.h"
|
||||
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/dom/nsIContentParent.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsITraceableChannel.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsStringStream.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace extensions {
|
||||
|
||||
/*****************************************************************************
|
||||
* Initialization
|
||||
*****************************************************************************/
|
||||
|
||||
StreamFilterParent::StreamFilterParent(uint64_t aChannelId, const nsAString& aAddonId)
|
||||
: mChannelId(aChannelId)
|
||||
, mAddonId(NS_Atomize(aAddonId))
|
||||
, mPBackgroundThread(NS_GetCurrentThread())
|
||||
, mIOThread(do_GetMainThread())
|
||||
, mBufferMutex("StreamFilter buffer mutex")
|
||||
, mReceivedStop(false)
|
||||
, mSentStop(false)
|
||||
, mContext(nullptr)
|
||||
, mOffset(0)
|
||||
, mState(State::Uninitialized)
|
||||
{
|
||||
}
|
||||
|
||||
StreamFilterParent::~StreamFilterParent()
|
||||
{
|
||||
NS_ReleaseOnMainThreadSystemGroup("StreamFilterParent::mOrigListener",
|
||||
mOrigListener.forget());
|
||||
NS_ReleaseOnMainThreadSystemGroup("StreamFilterParent::mContext",
|
||||
mContext.forget());
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterParent::Init(already_AddRefed<nsIContentParent> aContentParent)
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
SystemGroup::Dispatch(
|
||||
TaskCategory::Network,
|
||||
NewRunnableMethod<already_AddRefed<nsIContentParent>&&>(
|
||||
"StreamFilterParent::DoInit",
|
||||
this, &StreamFilterParent::DoInit, Move(aContentParent)));
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterParent::DoInit(already_AddRefed<nsIContentParent>&& aContentParent)
|
||||
{
|
||||
AssertIsMainThread();
|
||||
|
||||
nsCOMPtr<nsIContentParent> contentParent = aContentParent;
|
||||
|
||||
bool success = false;
|
||||
auto guard = MakeScopeExit([&] {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->mState = State::Initialized;
|
||||
self->CheckResult(self->SendInitialized(success));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
auto& webreq = WebRequestService::GetSingleton();
|
||||
|
||||
mChannel = webreq.GetTraceableChannel(mChannelId, mAddonId, contentParent);
|
||||
if (NS_WARN_IF(!mChannel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsITraceableChannel> traceable = do_QueryInterface(mChannel);
|
||||
MOZ_RELEASE_ASSERT(traceable);
|
||||
|
||||
nsresult rv = traceable->SetNewListener(this, getter_AddRefs(mOrigListener));
|
||||
success = NS_SUCCEEDED(rv);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* nsIThreadRetargetableStreamListener
|
||||
*****************************************************************************/
|
||||
|
||||
NS_IMETHODIMP
|
||||
StreamFilterParent::CheckListenerChain()
|
||||
{
|
||||
AssertIsMainThread();
|
||||
|
||||
nsCOMPtr<nsIThreadRetargetableStreamListener> trsl =
|
||||
do_QueryInterface(mOrigListener);
|
||||
if (trsl) {
|
||||
return trsl->CheckListenerChain();
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Error handling
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterParent::Broken()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
mState = State::Disconnecting;
|
||||
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnIOThread(FUNC, [=] {
|
||||
self->FlushBufferedData();
|
||||
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->mState = State::Disconnected;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* State change requests
|
||||
*****************************************************************************/
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvClose()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
mState = State::Closed;
|
||||
|
||||
if (!mSentStop) {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnMainThread(FUNC, [=] {
|
||||
nsresult rv = self->EmitStopRequest(NS_OK);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
});
|
||||
}
|
||||
|
||||
Unused << SendClosed();
|
||||
Unused << Send__delete__(this);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvSuspend()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
if (mState == State::TransferringData) {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnMainThread(FUNC, [=] {
|
||||
self->mChannel->Suspend();
|
||||
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->mState = State::Suspended;
|
||||
self->CheckResult(self->SendSuspended());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvResume()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
if (mState == State::Suspended) {
|
||||
// Change state before resuming so incoming data is handled correctly
|
||||
// immediately after resuming.
|
||||
mState = State::TransferringData;
|
||||
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnMainThread(FUNC, [=] {
|
||||
self->mChannel->Resume();
|
||||
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->CheckResult(self->SendResumed());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvDisconnect()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
if (mState == State::Suspended) {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnMainThread(FUNC, [=] {
|
||||
self->mChannel->Resume();
|
||||
});
|
||||
} else if (mState != State::TransferringData) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mState = State::Disconnecting;
|
||||
CheckResult(SendFlushData());
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvFlushedData()
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
MOZ_ASSERT(mState == State::Disconnecting);
|
||||
|
||||
Unused << Send__delete__(this);
|
||||
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnIOThread(FUNC, [=] {
|
||||
self->FlushBufferedData();
|
||||
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
self->mState = State::Disconnected;
|
||||
});
|
||||
});
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Data output
|
||||
*****************************************************************************/
|
||||
|
||||
IPCResult
|
||||
StreamFilterParent::RecvWrite(Data&& aData)
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
mIOThread->Dispatch(
|
||||
NewRunnableMethod<Data&&>("StreamFilterParent::WriteMove",
|
||||
this,
|
||||
&StreamFilterParent::WriteMove,
|
||||
Move(aData)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
void
|
||||
StreamFilterParent::WriteMove(Data&& aData)
|
||||
{
|
||||
nsresult rv = Write(aData);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
nsresult
|
||||
StreamFilterParent::Write(Data& aData)
|
||||
{
|
||||
AssertIsIOThread();
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
|
||||
reinterpret_cast<char*>(aData.Elements()),
|
||||
aData.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mOrigListener->OnDataAvailable(mChannel, mContext, stream,
|
||||
mOffset, aData.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mOffset += aData.Length();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* nsIStreamListener
|
||||
*****************************************************************************/
|
||||
|
||||
NS_IMETHODIMP
|
||||
StreamFilterParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
{
|
||||
AssertIsMainThread();
|
||||
|
||||
mContext = aContext;
|
||||
|
||||
if (mState != State::Disconnected) {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->mState = State::TransferringData;
|
||||
self->CheckResult(self->SendStartRequest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mOrigListener->OnStartRequest(aRequest, aContext);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
StreamFilterParent::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatusCode)
|
||||
{
|
||||
AssertIsMainThread();
|
||||
|
||||
mReceivedStop = true;
|
||||
if (mState == State::Disconnected) {
|
||||
return EmitStopRequest(aStatusCode);
|
||||
}
|
||||
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnPBackgroundThread(FUNC, [=] {
|
||||
if (self->IPCActive()) {
|
||||
self->CheckResult(self->SendStopRequest(aStatusCode));
|
||||
}
|
||||
});
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
StreamFilterParent::EmitStopRequest(nsresult aStatusCode)
|
||||
{
|
||||
AssertIsMainThread();
|
||||
MOZ_ASSERT(!mSentStop);
|
||||
|
||||
mSentStop = true;
|
||||
return mOrigListener->OnStopRequest(mChannel, mContext, aStatusCode);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Incoming data handling
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterParent::DoSendData(Data&& aData)
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
if (mState == State::TransferringData) {
|
||||
CheckResult(SendData(aData));
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
StreamFilterParent::OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aInputStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
// Note: No AssertIsIOThread here. Whatever thread we're on now is, by
|
||||
// definition, the IO thread.
|
||||
mIOThread = NS_GetCurrentThread();
|
||||
|
||||
if (mState == State::Disconnected) {
|
||||
// If we're offloading data in a thread pool, it's possible that we'll
|
||||
// have buffered some additional data while waiting for the buffer to
|
||||
// flush. So, if there's any buffered data left, flush that before we
|
||||
// flush this incoming data.
|
||||
//
|
||||
// Note: When in the eDisconnected state, the buffer list is guaranteed
|
||||
// never to be accessed by another thread during an OnDataAvailable call.
|
||||
if (!mBufferedData.isEmpty()) {
|
||||
FlushBufferedData();
|
||||
}
|
||||
|
||||
mOffset += aCount;
|
||||
return mOrigListener->OnDataAvailable(aRequest, aContext, aInputStream,
|
||||
mOffset - aCount, aCount);
|
||||
}
|
||||
|
||||
Data data;
|
||||
data.SetLength(aCount);
|
||||
|
||||
uint32_t count;
|
||||
nsresult rv = aInputStream->Read(reinterpret_cast<char*>(data.Elements()),
|
||||
aCount, &count);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(count == aCount, NS_ERROR_UNEXPECTED);
|
||||
|
||||
if (mState == State::Disconnecting) {
|
||||
MutexAutoLock al(mBufferMutex);
|
||||
BufferData(Move(data));
|
||||
} else if (mState == State::Closed) {
|
||||
return NS_ERROR_FAILURE;
|
||||
} else {
|
||||
mPBackgroundThread->Dispatch(
|
||||
NewRunnableMethod<Data&&>("StreamFilterParent::DoSendData",
|
||||
this,
|
||||
&StreamFilterParent::DoSendData,
|
||||
Move(data)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
StreamFilterParent::FlushBufferedData()
|
||||
{
|
||||
AssertIsIOThread();
|
||||
|
||||
// When offloading data to a thread pool, OnDataAvailable isn't guaranteed
|
||||
// to always run in the same thread, so it's possible for this function to
|
||||
// run in parallel with OnDataAvailable.
|
||||
MutexAutoLock al(mBufferMutex);
|
||||
|
||||
while (!mBufferedData.isEmpty()) {
|
||||
UniquePtr<BufferedData> data(mBufferedData.popFirst());
|
||||
|
||||
nsresult rv = Write(data->mData);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (mReceivedStop && !mSentStop) {
|
||||
RefPtr<StreamFilterParent> self(this);
|
||||
RunOnMainThread(FUNC, [=] {
|
||||
if (!mSentStop) {
|
||||
nsresult rv = self->EmitStopRequest(NS_OK);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Glue
|
||||
*****************************************************************************/
|
||||
|
||||
void
|
||||
StreamFilterParent::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsPBackgroundThread();
|
||||
|
||||
if (mState != State::Disconnected && mState != State::Closed) {
|
||||
Broken();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(StreamFilterParent, nsIStreamListener, nsIRequestObserver, nsIThreadRetargetableStreamListener)
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_extensions_StreamFilterParent_h
|
||||
#define mozilla_extensions_StreamFilterParent_h
|
||||
|
||||
#include "StreamFilterBase.h"
|
||||
#include "mozilla/extensions/PStreamFilterParent.h"
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/SystemGroup.h"
|
||||
#include "mozilla/WebRequestService.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsIThreadRetargetableStreamListener.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define FUNC __FUNCSIG__
|
||||
#else
|
||||
# define FUNC __PRETTY_FUNCTION__
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class nsIContentParent;
|
||||
}
|
||||
|
||||
namespace extensions {
|
||||
|
||||
using namespace mozilla::dom;
|
||||
using mozilla::ipc::IPCResult;
|
||||
|
||||
class StreamFilterParent final
|
||||
: public PStreamFilterParent
|
||||
, public nsIStreamListener
|
||||
, public nsIThreadRetargetableStreamListener
|
||||
, public StreamFilterBase
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
|
||||
|
||||
explicit StreamFilterParent(uint64_t aChannelId, const nsAString& aAddonId);
|
||||
|
||||
enum class State
|
||||
{
|
||||
// The parent has been created, but not yet constructed by the child.
|
||||
Uninitialized,
|
||||
// The parent has been successfully constructed.
|
||||
Initialized,
|
||||
// The OnRequestStarted event has been received, and data is being
|
||||
// transferred to the child.
|
||||
TransferringData,
|
||||
// The channel is suspended.
|
||||
Suspended,
|
||||
// The channel has been closed by the child, and will send or receive data.
|
||||
Closed,
|
||||
// The channel is being disconnected from the child, so that all further
|
||||
// data and events pass unfiltered to the output listener. Any data
|
||||
// currnetly in transit to, or buffered by, the child will be written to the
|
||||
// output listener before we enter the Disconnected atate.
|
||||
Disconnecting,
|
||||
// The channel has been disconnected from the child, and all further data
|
||||
// and events will be passed directly to the output listener.
|
||||
Disconnected,
|
||||
};
|
||||
|
||||
static already_AddRefed<StreamFilterParent>
|
||||
Create(uint64_t aChannelId, const nsAString& aAddonId)
|
||||
{
|
||||
RefPtr<StreamFilterParent> filter = new StreamFilterParent(aChannelId, aAddonId);
|
||||
return filter.forget();
|
||||
}
|
||||
|
||||
void Init(already_AddRefed<nsIContentParent> aContentParent);
|
||||
|
||||
protected:
|
||||
virtual ~StreamFilterParent();
|
||||
|
||||
virtual IPCResult RecvWrite(Data&& aData) override;
|
||||
virtual IPCResult RecvFlushedData() override;
|
||||
virtual IPCResult RecvSuspend() override;
|
||||
virtual IPCResult RecvResume() override;
|
||||
virtual IPCResult RecvClose() override;
|
||||
virtual IPCResult RecvDisconnect() override;
|
||||
|
||||
private:
|
||||
bool IPCActive()
|
||||
{
|
||||
return (mState != State::Closed &&
|
||||
mState != State::Disconnecting &&
|
||||
mState != State::Disconnected);
|
||||
}
|
||||
|
||||
void DoInit(already_AddRefed<nsIContentParent>&& aContentParent);
|
||||
|
||||
nsresult FlushBufferedData();
|
||||
|
||||
nsresult Write(Data& aData);
|
||||
|
||||
void WriteMove(Data&& aData);
|
||||
|
||||
void DoSendData(Data&& aData);
|
||||
|
||||
nsresult EmitStopRequest(nsresult aStatusCode);
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
void Broken();
|
||||
|
||||
void
|
||||
CheckResult(bool aResult)
|
||||
{
|
||||
if (NS_WARN_IF(!aResult)) {
|
||||
Broken();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AssertIsPBackgroundThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mPBackgroundThread);
|
||||
}
|
||||
|
||||
void
|
||||
AssertIsIOThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mIOThread);
|
||||
}
|
||||
|
||||
void
|
||||
AssertIsMainThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
void
|
||||
RunOnMainThread(const char* aName, Function&& aFunc)
|
||||
{
|
||||
SystemGroup::Dispatch(TaskCategory::Network,
|
||||
Move(NS_NewRunnableFunction(aName, aFunc)));
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
void
|
||||
RunOnPBackgroundThread(const char* aName, Function&& aFunc)
|
||||
{
|
||||
mPBackgroundThread->Dispatch(Move(NS_NewRunnableFunction(aName, aFunc)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
void
|
||||
RunOnIOThread(const char* aName, Function&& aFunc)
|
||||
{
|
||||
mIOThread->Dispatch(Move(NS_NewRunnableFunction(aName, aFunc)),
|
||||
NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
const uint64_t mChannelId;
|
||||
const nsCOMPtr<nsIAtom> mAddonId;
|
||||
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsCOMPtr<nsIStreamListener> mOrigListener;
|
||||
|
||||
nsCOMPtr<nsIThread> mPBackgroundThread;
|
||||
nsCOMPtr<nsIThread> mIOThread;
|
||||
|
||||
Mutex mBufferMutex;
|
||||
|
||||
bool mReceivedStop;
|
||||
bool mSentStop;
|
||||
|
||||
nsCOMPtr<nsISupports> mContext;
|
||||
uint64_t mOffset;
|
||||
|
||||
volatile State mState;
|
||||
};
|
||||
|
||||
} // namespace extensions
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_extensions_StreamFilterParent_h
|
|
@ -0,0 +1,163 @@
|
|||
/* -*- 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 "WebRequestService.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsITabParent.h"
|
||||
#include "nsITraceableChannel.h"
|
||||
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
WebRequestService::WebRequestService()
|
||||
: mDataLock("WebRequest service data lock")
|
||||
{
|
||||
}
|
||||
|
||||
WebRequestService::~WebRequestService()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebRequestService, mozIWebRequestService)
|
||||
|
||||
/* static */ WebRequestService&
|
||||
WebRequestService::GetSingleton()
|
||||
{
|
||||
static RefPtr<WebRequestService> instance;
|
||||
if (!instance) {
|
||||
instance = new WebRequestService();
|
||||
ClearOnShutdown(&instance);
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebRequestService::RegisterTraceableChannel(uint64_t aChannelId,
|
||||
nsIChannel* aChannel,
|
||||
const nsAString& aAddonId,
|
||||
nsITabParent* aTabParent,
|
||||
nsIJSRAIIHelper** aHelper)
|
||||
{
|
||||
nsCOMPtr<nsITraceableChannel> traceableChannel = do_QueryInterface(aChannel);
|
||||
NS_ENSURE_TRUE(traceableChannel, NS_ERROR_INVALID_ARG);
|
||||
|
||||
nsCOMPtr<nsIAtom> addonId = NS_Atomize(aAddonId);
|
||||
ChannelParent* entry = new ChannelParent(aChannelId, aChannel,
|
||||
addonId, aTabParent);
|
||||
|
||||
RefPtr<Destructor> destructor = new Destructor(entry);
|
||||
destructor.forget(aHelper);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIChannel>
|
||||
WebRequestService::GetTraceableChannel(uint64_t aChannelId,
|
||||
nsIAtom* aAddonId,
|
||||
nsIContentParent* aContentParent)
|
||||
{
|
||||
MutexAutoLock al(mDataLock);
|
||||
|
||||
auto entry = mChannelEntries.Get(aChannelId);
|
||||
if (!entry) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto channelEntry : entry->mTabParents) {
|
||||
nsIContentParent* contentParent = nullptr;
|
||||
if (channelEntry->mTabParent) {
|
||||
contentParent = static_cast<nsIContentParent*>(
|
||||
channelEntry->mTabParent->Manager());
|
||||
}
|
||||
|
||||
if (channelEntry->mAddonId == aAddonId && contentParent == aContentParent) {
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryReferent(entry->mChannel);
|
||||
return channel.forget();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
WebRequestService::ChannelParent::ChannelParent(uint64_t aChannelId, nsIChannel* aChannel,
|
||||
nsIAtom* aAddonId, nsITabParent* aTabParent)
|
||||
: mTabParent(static_cast<TabParent*>(aTabParent))
|
||||
, mAddonId(aAddonId)
|
||||
, mChannelId(aChannelId)
|
||||
{
|
||||
auto service = &GetSingleton();
|
||||
MutexAutoLock al(service->mDataLock);
|
||||
|
||||
auto entry = service->mChannelEntries.LookupOrAdd(mChannelId);
|
||||
|
||||
entry->mChannel = do_GetWeakReference(aChannel);
|
||||
entry->mTabParents.insertBack(this);
|
||||
}
|
||||
|
||||
WebRequestService::ChannelParent::~ChannelParent()
|
||||
{
|
||||
MOZ_ASSERT(mDetached);
|
||||
}
|
||||
|
||||
void
|
||||
WebRequestService::ChannelParent::Detach()
|
||||
{
|
||||
if (mDetached) {
|
||||
return;
|
||||
}
|
||||
auto service = &GetSingleton();
|
||||
MutexAutoLock al(service->mDataLock);
|
||||
|
||||
auto& map = service->mChannelEntries;
|
||||
auto entry = map.Get(mChannelId);
|
||||
MOZ_ASSERT(entry);
|
||||
|
||||
removeFrom(entry->mTabParents);
|
||||
if (entry->mTabParents.isEmpty()) {
|
||||
map.Remove(mChannelId);
|
||||
}
|
||||
mDetached = true;
|
||||
}
|
||||
|
||||
WebRequestService::ChannelEntry::~ChannelEntry()
|
||||
{
|
||||
while (ChannelParent* parent = mTabParents.getFirst()) {
|
||||
parent->Detach();
|
||||
}
|
||||
}
|
||||
|
||||
WebRequestService::Destructor::~Destructor()
|
||||
{
|
||||
if (NS_WARN_IF(!mDestructCalled)) {
|
||||
Destruct();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebRequestService::Destructor::Destruct()
|
||||
{
|
||||
if (NS_WARN_IF(mDestructCalled)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mDestructCalled = true;
|
||||
|
||||
mChannelParent->Detach();
|
||||
delete mChannelParent;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebRequestService::Destructor, nsIJSRAIIHelper)
|
|
@ -0,0 +1,103 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_WebRequestService_h
|
||||
#define mozilla_WebRequestService_h
|
||||
|
||||
#include "mozIWebRequestService.h"
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsIAtom.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
#include "nsWeakPtr.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class TabParent;
|
||||
class nsIContentParent;
|
||||
}
|
||||
}
|
||||
|
||||
class WebRequestService : public mozIWebRequestService
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MOZIWEBREQUESTSERVICE
|
||||
|
||||
explicit WebRequestService();
|
||||
|
||||
static already_AddRefed<WebRequestService> GetInstance()
|
||||
{
|
||||
return do_AddRef(&GetSingleton());
|
||||
}
|
||||
|
||||
static WebRequestService& GetSingleton();
|
||||
|
||||
already_AddRefed<nsIChannel>
|
||||
GetTraceableChannel(uint64_t aChannelId, nsIAtom* aAddonId,
|
||||
dom::nsIContentParent* aContentParent);
|
||||
|
||||
protected:
|
||||
virtual ~WebRequestService();
|
||||
|
||||
private:
|
||||
class ChannelParent : public LinkedListElement<ChannelParent>
|
||||
{
|
||||
public:
|
||||
explicit ChannelParent(uint64_t aChannelId, nsIChannel* aChannel, nsIAtom* aAddonId, nsITabParent* aTabParent);
|
||||
~ChannelParent();
|
||||
|
||||
void Detach();
|
||||
|
||||
const RefPtr<dom::TabParent> mTabParent;
|
||||
const nsCOMPtr<nsIAtom> mAddonId;
|
||||
|
||||
private:
|
||||
const uint64_t mChannelId;
|
||||
bool mDetached = false;
|
||||
};
|
||||
|
||||
class Destructor : public nsIJSRAIIHelper
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIJSRAIIHELPER
|
||||
|
||||
explicit Destructor(ChannelParent* aChannelParent)
|
||||
: mChannelParent(aChannelParent)
|
||||
, mDestructCalled(false)
|
||||
{}
|
||||
|
||||
protected:
|
||||
virtual ~Destructor();
|
||||
|
||||
private:
|
||||
ChannelParent* mChannelParent;
|
||||
bool mDestructCalled;
|
||||
};
|
||||
|
||||
class ChannelEntry
|
||||
{
|
||||
public:
|
||||
~ChannelEntry();
|
||||
// Note: We can't keep a strong pointer to the channel here, since channels
|
||||
// are not cycle collected, and a reference to this object will be stored on
|
||||
// the channel in order to keep the entry alive.
|
||||
nsWeakPtr mChannel;
|
||||
LinkedList<ChannelParent> mTabParents;
|
||||
};
|
||||
|
||||
nsClassHashtable<nsUint64HashKey, ChannelEntry> mChannelEntries;
|
||||
Mutex mDataLock;
|
||||
};
|
||||
|
||||
#endif // mozilla_WebRequestService_h
|
|
@ -5,17 +5,44 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'mozIWebRequestService.idl',
|
||||
'nsIWebRequestListener.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'webextensions'
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsWebRequestListener.cpp',
|
||||
'StreamFilter.cpp',
|
||||
'StreamFilterChild.cpp',
|
||||
'StreamFilterEvents.cpp',
|
||||
'StreamFilterParent.cpp',
|
||||
'WebRequestService.cpp',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'PStreamFilter.ipdl',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'nsWebRequestListener.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsWebRequestListener.cpp',
|
||||
EXPORTS.mozilla += [
|
||||
'WebRequestService.h',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
EXPORTS.mozilla.extensions += [
|
||||
'StreamFilter.h',
|
||||
'StreamFilterBase.h',
|
||||
'StreamFilterChild.h',
|
||||
'StreamFilterEvents.h',
|
||||
'StreamFilterParent.h',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Toolkit", "WebExtensions: Request Handling")
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/* 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 "nsISupports.idl"
|
||||
|
||||
interface nsIChannel;
|
||||
interface nsIJSRAIIHelper;
|
||||
interface nsITabParent;
|
||||
|
||||
[scriptable, builtinclass, uuid(1b1118ed-f208-4cfc-b841-5b31a78c2b7a)]
|
||||
interface mozIWebRequestService : nsISupports
|
||||
{
|
||||
nsIJSRAIIHelper registerTraceableChannel(in uint64_t channelId,
|
||||
in nsIChannel channel,
|
||||
in AString addonId,
|
||||
[optional] in nsITabParent tabParent);
|
||||
};
|
|
@ -29,6 +29,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestUpload",
|
||||
"resource://gre/modules/WebRequestUpload.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "webReqService",
|
||||
"@mozilla.org/addons/webrequest-service;1",
|
||||
"mozIWebRequestService");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "ExtensionError", () => ExtensionUtils.ExtensionError);
|
||||
|
||||
let WebRequestListener = Components.Constructor("@mozilla.org/webextensions/webRequestListener;1",
|
||||
|
@ -52,7 +56,8 @@ function extractFromChannel(channel, key) {
|
|||
|
||||
function getData(channel) {
|
||||
const key = "mozilla.webRequest.data";
|
||||
return extractFromChannel(channel, key) || attachToChannel(channel, key, {});
|
||||
return (extractFromChannel(channel, key) ||
|
||||
attachToChannel(channel, key, {registeredFilters: new Map()}));
|
||||
}
|
||||
|
||||
function getFinalChannelURI(channel) {
|
||||
|
@ -89,7 +94,7 @@ function parseFilter(filter) {
|
|||
return {urls: filter.urls || null, types: filter.types || null};
|
||||
}
|
||||
|
||||
function parseExtra(extra, allowed = []) {
|
||||
function parseExtra(extra, allowed = [], optionsObj = {}) {
|
||||
if (extra) {
|
||||
for (let ex of extra) {
|
||||
if (allowed.indexOf(ex) == -1) {
|
||||
|
@ -98,7 +103,7 @@ function parseExtra(extra, allowed = []) {
|
|||
}
|
||||
}
|
||||
|
||||
let result = {};
|
||||
let result = Object.assign({}, optionsObj);
|
||||
for (let al of allowed) {
|
||||
if (extra && extra.indexOf(al) != -1) {
|
||||
result[al] = true;
|
||||
|
@ -577,9 +582,15 @@ HttpObserverManager = {
|
|||
this.modifyInitialized = false;
|
||||
Services.obs.removeObserver(this, "http-on-before-connect");
|
||||
}
|
||||
|
||||
let haveBlocking = Object.values(this.listeners)
|
||||
.some(listeners => Array.from(listeners.values())
|
||||
.some(listener => listener.blockingAllowed));
|
||||
|
||||
this.needTracing = this.listeners.onStart.size ||
|
||||
this.listeners.onError.size ||
|
||||
this.listeners.onStop.size;
|
||||
this.listeners.onStop.size ||
|
||||
haveBlocking;
|
||||
|
||||
let needExamine = this.needTracing ||
|
||||
this.listeners.headersReceived.size ||
|
||||
|
@ -597,7 +608,7 @@ HttpObserverManager = {
|
|||
Services.obs.removeObserver(this, "http-on-examine-merged-response");
|
||||
}
|
||||
|
||||
let needRedirect = this.listeners.onRedirect.size;
|
||||
let needRedirect = this.listeners.onRedirect.size || haveBlocking;
|
||||
if (needRedirect && !this.redirectInitialized) {
|
||||
this.redirectInitialized = true;
|
||||
ChannelEventSink.register();
|
||||
|
@ -886,6 +897,33 @@ HttpObserverManager = {
|
|||
return true;
|
||||
},
|
||||
|
||||
registerChannel(channel, opts) {
|
||||
if (!opts.blockingAllowed || !opts.addonId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = getData(channel);
|
||||
if (data.registeredFilters.has(opts.addonId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filter = webReqService.registerTraceableChannel(
|
||||
parseInt(data.requestId, 10),
|
||||
channel,
|
||||
opts.addonId,
|
||||
opts.tabParent);
|
||||
|
||||
data.registeredFilters.set(opts.addonId, filter);
|
||||
},
|
||||
|
||||
destroyFilters(channel) {
|
||||
let filters = getData(channel).registeredFilters;
|
||||
for (let [key, filter] of filters.entries()) {
|
||||
filter.destruct();
|
||||
filters.delete(key);
|
||||
}
|
||||
},
|
||||
|
||||
runChannelListener(channel, loadContext = null, kind, extraData = null) {
|
||||
let handlerResults = [];
|
||||
let requestHeaders;
|
||||
|
@ -895,6 +933,7 @@ HttpObserverManager = {
|
|||
if (this.activityInitialized) {
|
||||
let channelData = getData(channel);
|
||||
if (kind === "onError") {
|
||||
this.destroyFilters(channel);
|
||||
if (channelData.errorNotified) {
|
||||
return;
|
||||
}
|
||||
|
@ -908,8 +947,8 @@ HttpObserverManager = {
|
|||
let policyType = (loadInfo ? loadInfo.externalContentPolicyType
|
||||
: Ci.nsIContentPolicy.TYPE_OTHER);
|
||||
|
||||
let includeStatus = (["headersReceived", "authRequired", "onRedirect", "onStart", "onStop"].includes(kind) &&
|
||||
channel instanceof Ci.nsIHttpChannel);
|
||||
let includeStatus = ["headersReceived", "authRequired", "onRedirect", "onStart", "onStop"].includes(kind);
|
||||
let registerFilter = ["opening", "modify", "afterModify", "headersReceived", "authRequired", "onRedirect"].includes(kind);
|
||||
|
||||
let canModify = this.canModify(channel);
|
||||
let commonData = null;
|
||||
|
@ -925,6 +964,10 @@ HttpObserverManager = {
|
|||
}
|
||||
let data = Object.assign({}, commonData);
|
||||
|
||||
if (registerFilter) {
|
||||
this.registerChannel(channel, opts);
|
||||
}
|
||||
|
||||
if (opts.requestHeaders) {
|
||||
requestHeaders = requestHeaders || new RequestHeaderChanger(channel);
|
||||
data.requestHeaders = requestHeaders.toArray();
|
||||
|
@ -1096,11 +1139,13 @@ HttpObserverManager = {
|
|||
onChannelReplaced(oldChannel, newChannel) {
|
||||
// We want originalURI, this will provide a moz-ext rather than jar or file
|
||||
// uri on redirects.
|
||||
this.destroyFilters(oldChannel);
|
||||
this.runChannelListener(oldChannel, this.getLoadContext(oldChannel),
|
||||
"onRedirect", {redirectUrl: newChannel.originalURI.spec});
|
||||
},
|
||||
|
||||
onStartRequest(channel, loadContext) {
|
||||
this.destroyFilters(channel);
|
||||
this.runChannelListener(channel, loadContext, "onStart");
|
||||
},
|
||||
|
||||
|
@ -1112,10 +1157,12 @@ HttpObserverManager = {
|
|||
var onBeforeRequest = {
|
||||
allowedOptions: ["blocking", "requestBody"],
|
||||
|
||||
addListener(callback, filter = null, opt_extraInfoSpec = null) {
|
||||
let opts = parseExtra(opt_extraInfoSpec, this.allowedOptions);
|
||||
addListener(callback, filter = null, options = null, optionsObject = null) {
|
||||
let opts = parseExtra(options, this.allowedOptions);
|
||||
opts.filter = parseFilter(filter);
|
||||
ContentPolicyManager.addListener(callback, opts);
|
||||
|
||||
opts = Object.assign({}, opts, optionsObject);
|
||||
HttpObserverManager.addListener("opening", callback, opts);
|
||||
},
|
||||
|
||||
|
@ -1131,8 +1178,8 @@ function HttpEvent(internalEvent, options) {
|
|||
}
|
||||
|
||||
HttpEvent.prototype = {
|
||||
addListener(callback, filter = null, opt_extraInfoSpec = null) {
|
||||
let opts = parseExtra(opt_extraInfoSpec, this.options);
|
||||
addListener(callback, filter = null, options = null, optionsObject = null) {
|
||||
let opts = parseExtra(options, this.options, optionsObject);
|
||||
opts.filter = parseFilter(filter);
|
||||
HttpObserverManager.addListener(this.internalEvent, callback, opts);
|
||||
},
|
||||
|
@ -1145,7 +1192,7 @@ HttpEvent.prototype = {
|
|||
var onBeforeSendHeaders = new HttpEvent("modify", ["requestHeaders", "blocking"]);
|
||||
var onSendHeaders = new HttpEvent("afterModify", ["requestHeaders"]);
|
||||
var onHeadersReceived = new HttpEvent("headersReceived", ["blocking", "responseHeaders"]);
|
||||
var onAuthRequired = new HttpEvent("authRequired", ["blocking", "responseHeaders"]); // TODO asyncBlocking
|
||||
var onAuthRequired = new HttpEvent("authRequired", ["blocking", "responseHeaders"]);
|
||||
var onBeforeRedirect = new HttpEvent("onRedirect", ["responseHeaders"]);
|
||||
var onResponseStarted = new HttpEvent("onStart", ["responseHeaders"]);
|
||||
var onCompleted = new HttpEvent("onStop", ["responseHeaders"]);
|
||||
|
|
Загрузка…
Ссылка в новой задаче