Skip to content

Commit

Permalink
feat: Add support to callback in protocol interceptor handler without…
Browse files Browse the repository at this point in the history
… argument to perform as if no interceptors.
  • Loading branch information
mrchaofan committed Apr 26, 2024
1 parent b41da15 commit b9196c2
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 22 deletions.
54 changes: 35 additions & 19 deletions shell/browser/net/electron_url_loader_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,7 @@ void ElectronURLLoaderFactory::OnComplete(
}
}

// static
void ElectronURLLoaderFactory::StartLoading(
void ElectronURLLoaderFactory::StartLoadingWithResponse(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
Expand All @@ -326,20 +325,10 @@ void ElectronURLLoaderFactory::StartLoading(
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args) {
// Send network error when there is no argument passed.
//
// Note that we should not throw JS error in the callback no matter what is
// passed, to keep compatibility with old code.
v8::Local<v8::Value> response;
if (!args->GetNext(&response)) {
OnComplete(std::move(client), request_id,
network::URLLoaderCompletionStatus(net::ERR_NOT_IMPLEMENTED));
return;
}

v8::Isolate* isolate,
v8::Local<v8::Value> response) {
// Parse {error} object.
gin_helper::Dictionary dict = ToDict(args->isolate(), response);
gin_helper::Dictionary dict = ToDict(isolate, response);
if (!dict.IsEmpty()) {
int error_code;
if (dict.Get("error", &error_code)) {
Expand Down Expand Up @@ -417,7 +406,7 @@ void ElectronURLLoaderFactory::StartLoading(
break;
case ProtocolType::kString: {
std::string data;
if (gin::ConvertFromV8(args->isolate(), response, &data))
if (gin::ConvertFromV8(isolate, response, &data))
SendContents(std::move(client), std::move(head), data);
else if (!dict.IsEmpty() && dict.Get("data", &data))
SendContents(std::move(client), std::move(head), data);
Expand All @@ -428,7 +417,7 @@ void ElectronURLLoaderFactory::StartLoading(
}
case ProtocolType::kFile: {
base::FilePath path;
if (gin::ConvertFromV8(args->isolate(), response, &path))
if (gin::ConvertFromV8(isolate, response, &path))
StartLoadingFile(std::move(client), std::move(loader), std::move(head),
request, path, dict);
else if (!dict.IsEmpty() && dict.Get("path", &path))
Expand Down Expand Up @@ -466,8 +455,8 @@ void ElectronURLLoaderFactory::StartLoading(
data.As<v8::ArrayBufferView>());
} else if (data->IsString()) {
SendContents(std::move(client), std::move(head),
gin::V8ToString(args->isolate(), data));
} else if (LooksLikeStream(args->isolate(), data)) {
gin::V8ToString(isolate, data));
} else if (LooksLikeStream(isolate, data)) {
StartLoadingStream(std::move(client), std::move(loader),
std::move(head), dict);
} else if (!dict.IsEmpty()) {
Expand All @@ -492,6 +481,33 @@ void ElectronURLLoaderFactory::StartLoading(
}
}

// static
void ElectronURLLoaderFactory::StartLoading(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args) {
// Send network error when there is no argument passed.
//
// Note that we should not throw JS error in the callback no matter what is
// passed, to keep compatibility with old code.
v8::Local<v8::Value> response;
if (!args->GetNext(&response)) {
OnComplete(std::move(client), request_id,
network::URLLoaderCompletionStatus(net::ERR_NOT_IMPLEMENTED));
return;
}
ElectronURLLoaderFactory::StartLoadingWithResponse(
std::move(loader), request_id, options, request, std::move(client),
traffic_annotation, std::move(target_factory), type, args->isolate(),
response);
}

// static
void ElectronURLLoaderFactory::StartLoadingBuffer(
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
Expand Down
16 changes: 14 additions & 2 deletions shell/browser/net/electron_url_loader_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
override;

static void StartLoading(
static void StartLoadingWithResponse(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
Expand All @@ -118,7 +118,8 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args);
v8::Isolate* isolate,
v8::Local<v8::Value> response);

// disable copy
ElectronURLLoaderFactory(const ElectronURLLoaderFactory&) = delete;
Expand All @@ -131,6 +132,17 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver);
~ElectronURLLoaderFactory() override;

static void StartLoading(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args);

static void OnComplete(
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
int32_t request_id,
Expand Down
34 changes: 33 additions & 1 deletion shell/browser/net/proxying_url_loader_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,38 @@ bool ProxyingURLLoaderFactory::ShouldIgnoreConnectionsLimit(
return false;
}

// static
void ProxyingURLLoaderFactory::StartLoading(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args) {
v8::Local<v8::Value> response;
if (!args->GetNext(&response) || response->IsNullOrUndefined() ||
(response->IsObject() &&
response.As<v8::Object>()
->GetOwnPropertyNames(args->isolate()->GetCurrentContext())
.ToLocalChecked()
->Length() == 0)) {
mojo::Remote<network::mojom::URLLoaderFactory> target_factory_remote(
std::move(target_factory));
// trigger receiver of itself but with kBypassCustomProtocolHandlers
target_factory_remote->CreateLoaderAndStart(
std::move(loader), request_id, options | kBypassCustomProtocolHandlers,
request, std::move(client), traffic_annotation);
return;
}
ElectronURLLoaderFactory::StartLoadingWithResponse(
std::move(loader), request_id, options, request, std::move(client),
traffic_annotation, std::move(target_factory), type, args->isolate(),
response);
}

void ProxyingURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
Expand Down Expand Up @@ -816,7 +848,7 @@ void ProxyingURLLoaderFactory::CreateLoaderAndStart(
// <scheme, <type, handler>>
it->second.second.Run(
request,
base::BindOnce(&ElectronURLLoaderFactory::StartLoading,
base::BindOnce(&ProxyingURLLoaderFactory::StartLoading,
std::move(loader), request_id, options, request,
std::move(client), traffic_annotation,
std::move(loader_remote), it->second.first));
Expand Down
10 changes: 10 additions & 0 deletions shell/browser/net/proxying_url_loader_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,16 @@ class ProxyingURLLoaderFactory
bool IsForServiceWorkerScript() const;

private:
static void StartLoading(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
ProtocolType type,
gin::Arguments* args);
void OnTargetFactoryError();
void OnProxyBindingError();
void RemoveRequest(int32_t network_service_request_id, uint64_t request_id);
Expand Down
71 changes: 71 additions & 0 deletions spec/api-protocol-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const interceptStringProtocol = protocol.interceptStringProtocol;
const interceptBufferProtocol = protocol.interceptBufferProtocol;
const interceptHttpProtocol = protocol.interceptHttpProtocol;
const interceptStreamProtocol = protocol.interceptStreamProtocol;
const interceptProtocol: (scheme: string, handler: (req: Electron.ProtocolRequest, callback: (res?: Electron.ProtocolResponse) => void) => void) => boolean = protocol.interceptProtocol;
const unregisterProtocol = protocol.unregisterProtocol;
const uninterceptProtocol = protocol.uninterceptProtocol;

Expand Down Expand Up @@ -750,6 +751,76 @@ describe('protocol module', () => {
});
});

describe('protocol.interceptProtocol', () => {
const text = 'Hi, Chaofan!';
it('callback with null can behave like no intercept', async () => {
const server = http.createServer((req, res) => {
res.end(text);
});
after(() => server.close());
const { url } = await listen(server);
interceptProtocol('http', (req, callback) => {
callback();
});
await contents.loadURL(url);
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal(text);
});

it('callback with {data} can response directly', async () => {
interceptProtocol('http', (req, callback) => {
callback({
data: 'hello'
});
});
await contents.loadURL('http://foo');
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal('hello');
});

it('callback with null can behave like no intercept - redirect case', async () => {
const server = http.createServer((req, res) => {
if (req.url === '/serverRedirect') {
res.statusCode = 301;
res.setHeader('Location', `${url}/foo`);
res.end();
} else {
res.end(text);
}
});
after(() => server.close());
const { url } = await listen(server);
interceptProtocol('http', (req, callback) => {
callback();
});
await contents.loadURL(`${url}/serverRedirect`);
// Redirect should change the page url if not We may met the situation that returns data from page B to page A.
expect(await contents.getURL()).to.equal(`${url}/foo`);
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal(text);
});

it('callback with null can behave like no intercept - post case', async () => {
const server = http.createServer((req, res) => {
let body = '';
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.end(body);
});
});
after(() => server.close());
const { url } = await listen(server);
interceptProtocol('http', (req, callback) => {
callback();
});

const r = await ajax(url, {
method: 'POST',
body: text
});
expect(r.data).to.equal(text);
});
});

describe('protocol.uninterceptProtocol', () => {
it('returns false when scheme does not exist', () => {
expect(uninterceptProtocol('not-exist')).to.equal(false);
Expand Down

0 comments on commit b9196c2

Please sign in to comment.