merge mozilla-inbound to mozilla-central. r=merge a=merge

MozReview-Commit-ID: EzZGZI0GBca
This commit is contained in:
Sebastian Hengst 2017-09-04 11:12:40 +02:00
Родитель 3110fdde9a 01f833d455
Коммит 65706a9c1e
39 изменённых файлов: 3206 добавлений и 36 удалений

Просмотреть файл

@ -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": [

Двоичные данные
toolkit/components/extensions/test/mochitest/lorem.html.gz Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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"]);