зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1600053 - [remote] Implement IO.read. r=remote-protocol-reviewers,baku,ato,maja_zf
This patch implements the IO.read() method to allow reading streams for files and blobs. Therefor all the methods in the IO domain need a registry for streams. Those have to be stored globally because they need to be kept existent across different client connections. Differential Revision: https://phabricator.services.mozilla.com/D55968 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
162084495c
Коммит
5a966347fc
|
@ -16,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
|
|||
Browser: "chrome://remote/content/domains/parent/Browser.jsm",
|
||||
Emulation: "chrome://remote/content/domains/parent/Emulation.jsm",
|
||||
Input: "chrome://remote/content/domains/parent/Input.jsm",
|
||||
IO: "chrome://remote/content/domains/parent/IO.jsm",
|
||||
Network: "chrome://remote/content/domains/parent/Network.jsm",
|
||||
Page: "chrome://remote/content/domains/parent/Page.jsm",
|
||||
Security: "chrome://remote/content/domains/parent/Security.jsm",
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["IO", "streamRegistry"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
OS: "resource://gre/modules/osfile.jsm",
|
||||
});
|
||||
|
||||
const { Domain } = ChromeUtils.import(
|
||||
"chrome://remote/content/domains/Domain.jsm"
|
||||
);
|
||||
const { StreamRegistry } = ChromeUtils.import(
|
||||
"chrome://remote/content/StreamRegistry.jsm"
|
||||
);
|
||||
|
||||
const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
// Global singleton for managing open streams
|
||||
const streamRegistry = new StreamRegistry();
|
||||
|
||||
class IO extends Domain {
|
||||
// commands
|
||||
|
||||
/**
|
||||
* Read a chunk of the stream.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.handle
|
||||
* Handle of the stream to read.
|
||||
* @param {number=} options.offset
|
||||
* Seek to the specified offset before reading. If not specificed,
|
||||
* proceed with offset following the last read.
|
||||
* Some types of streams may only support sequential reads.
|
||||
* @param {number=} options.size
|
||||
* Maximum number of bytes to read (left upon the agent
|
||||
* discretion if not specified).
|
||||
*
|
||||
* @return {string, boolean, boolean}
|
||||
* Data that were read, including flags for base64-encoded, and end-of-file reached.
|
||||
*/
|
||||
async read(options = {}) {
|
||||
const { handle, offset, size } = options;
|
||||
|
||||
if (typeof handle != "string") {
|
||||
throw new TypeError(`handle: string value expected`);
|
||||
}
|
||||
|
||||
const stream = streamRegistry.get(handle);
|
||||
const fileInfo = await stream.stat();
|
||||
|
||||
if (typeof offset != "undefined") {
|
||||
if (typeof offset != "number") {
|
||||
throw new TypeError(`offset: integer value expected`);
|
||||
}
|
||||
|
||||
// To keep compatibility with Chrome clip invalid offsets
|
||||
const seekTo = Math.max(0, Math.min(offset, fileInfo.size));
|
||||
await stream.setPosition(seekTo, OS.File.POS_START);
|
||||
}
|
||||
|
||||
const curPos = await stream.getPosition();
|
||||
const remainingBytes = fileInfo.size - curPos;
|
||||
|
||||
let chunkSize;
|
||||
if (typeof size != "undefined") {
|
||||
if (typeof size != "number") {
|
||||
throw new TypeError(`size: integer value expected`);
|
||||
}
|
||||
|
||||
// Chromium currently crashes for negative sizes (https://bit.ly/2P6h0Fv),
|
||||
// but might behave similar to the offset and clip invalid values
|
||||
chunkSize = Math.max(0, Math.min(size, remainingBytes));
|
||||
} else {
|
||||
chunkSize = Math.min(DEFAULT_CHUNK_SIZE, remainingBytes);
|
||||
}
|
||||
|
||||
const bytes = await stream.read(chunkSize);
|
||||
// Each UCS2 character has an upper byte of 0 and a lower byte matching
|
||||
// the binary data
|
||||
const data = btoa(String.fromCharCode.apply(null, bytes));
|
||||
|
||||
return {
|
||||
data,
|
||||
base64Encoded: true,
|
||||
eof: remainingBytes - bytes.length == 0,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ remote.jar:
|
|||
content/domains/parent/Browser.jsm (domains/parent/Browser.jsm)
|
||||
content/domains/parent/Emulation.jsm (domains/parent/Emulation.jsm)
|
||||
content/domains/parent/Input.jsm (domains/parent/Input.jsm)
|
||||
content/domains/parent/IO.jsm (domains/parent/IO.jsm)
|
||||
content/domains/parent/Network.jsm (domains/parent/Network.jsm)
|
||||
content/domains/parent/network/ChannelEventSink.jsm (domains/parent/network/ChannelEventSink.jsm)
|
||||
content/domains/parent/network/NetworkObserver.jsm (domains/parent/network/NetworkObserver.jsm)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const { RemoteAgentError } = ChromeUtils.import(
|
||||
"chrome://remote/content/Error.jsm"
|
||||
);
|
||||
|
@ -190,3 +192,52 @@ function timeoutPromise(ms) {
|
|||
function fail(message) {
|
||||
ok(false, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file with the specified contents.
|
||||
*
|
||||
* @param {string} contents
|
||||
* Contents of the file.
|
||||
* @param {Object} options
|
||||
* @param {string=} options.path
|
||||
* Path of the file. Defaults to the temporary directory.
|
||||
* @param {boolean=} options.remove
|
||||
* If true, automatically remove the file after the test. Defaults to true.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves {string}
|
||||
* Returns the final path of the created file.
|
||||
*/
|
||||
async function createFile(contents, options = {}) {
|
||||
let { path = null, remove = true } = options;
|
||||
|
||||
if (!path) {
|
||||
const basePath = OS.Path.join(OS.Constants.Path.tmpDir, "remote-agent.txt");
|
||||
const { file, path: tmpPath } = await OS.File.openUnique(basePath, {
|
||||
humanReadable: true,
|
||||
});
|
||||
await file.close();
|
||||
path = tmpPath;
|
||||
}
|
||||
|
||||
let encoder = new TextEncoder();
|
||||
let array = encoder.encode(contents);
|
||||
|
||||
const count = await OS.File.writeAtomic(path, array, {
|
||||
encoding: "utf-8",
|
||||
tmpPath: path + ".tmp",
|
||||
});
|
||||
is(count, contents.length, "All data has been written to file");
|
||||
|
||||
const file = await OS.File.open(path);
|
||||
|
||||
// Automatically remove the file once the test has finished
|
||||
if (remove) {
|
||||
registerCleanupFunction(async () => {
|
||||
await file.close();
|
||||
await OS.File.remove(path, { ignoreAbsent: true });
|
||||
});
|
||||
}
|
||||
|
||||
return { file, path };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
[DEFAULT]
|
||||
tags = remote
|
||||
subsuite = remote
|
||||
prefs = remote.enabled=true
|
||||
support-files =
|
||||
head.js
|
||||
!/remote/test/browser/chrome-remote-interface.js
|
||||
!/remote/test/browser/head.js
|
||||
|
||||
[browser_read.js]
|
|
@ -0,0 +1,130 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function seekByOffsets({ IO }) {
|
||||
const contents = "Lorem ipsum";
|
||||
const { handle } = await registerFileStream(contents);
|
||||
|
||||
for (const offset of [0, 5, 10, 100, 0, -1]) {
|
||||
const result = await IO.read({ handle, offset });
|
||||
ok(result.base64Encoded, `Data for offset ${offset} is base64 encoded`);
|
||||
ok(result.eof, `All data has been read for offset ${offset}`);
|
||||
is(
|
||||
atob(result.data),
|
||||
contents.substring(offset >= 0 ? offset : 0),
|
||||
`Found expected data for offset ${offset}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function remembersOffsetAfterRead({ IO }) {
|
||||
const contents = "Lorem ipsum";
|
||||
const { handle } = await registerFileStream(contents);
|
||||
|
||||
let expectedOffset = 0;
|
||||
const size = 3;
|
||||
do {
|
||||
const result = await IO.read({ handle, size });
|
||||
is(
|
||||
atob(result.data),
|
||||
contents.substring(expectedOffset, expectedOffset + size),
|
||||
`Found expected data for expectedOffset ${expectedOffset}`
|
||||
);
|
||||
ok(
|
||||
result.base64Encoded,
|
||||
`Data up to expected offset ${expectedOffset} is base64 encoded`
|
||||
);
|
||||
|
||||
is(
|
||||
result.eof,
|
||||
expectedOffset + size >= contents.length,
|
||||
`All data has been read up to expected offset ${expectedOffset}`
|
||||
);
|
||||
|
||||
expectedOffset = Math.min(expectedOffset + size, contents.length);
|
||||
} while (expectedOffset < contents.length);
|
||||
});
|
||||
|
||||
add_task(async function readBySize({ IO }) {
|
||||
const contents = "Lorem ipsum";
|
||||
const { handle } = await registerFileStream(contents);
|
||||
|
||||
for (const size of [0, 5, 10, 100, 0, -1]) {
|
||||
const result = await IO.read({ handle, offset: 0, size });
|
||||
ok(result.base64Encoded, `Data for size ${size} is base64 encoded`);
|
||||
is(
|
||||
result.eof,
|
||||
size >= contents.length,
|
||||
`All data has been read for size ${size}`
|
||||
);
|
||||
is(
|
||||
atob(result.data),
|
||||
contents.substring(0, size),
|
||||
`Found expected data for size ${size}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function unknownHandle({ IO }) {
|
||||
const handle = "1000000";
|
||||
|
||||
try {
|
||||
await IO.read({ handle });
|
||||
ok(false, "Read shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.startsWith(`Invalid stream handle`),
|
||||
"Error contains expected message"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function invalidHandleTypes({ IO }) {
|
||||
for (const handle of [null, true, 1, [], {}]) {
|
||||
try {
|
||||
await IO.read({ handle });
|
||||
ok(false, "Read shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.startsWith(`handle: string value expected`),
|
||||
"Error contains expected message"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function invalidOffsetTypes({ IO }) {
|
||||
const contents = "Lorem ipsum";
|
||||
const { handle } = await registerFileStream(contents);
|
||||
|
||||
for (const offset of [null, true, "1", [], {}]) {
|
||||
try {
|
||||
await IO.read({ handle, offset });
|
||||
ok(false, "Read shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.startsWith(`offset: integer value expected`),
|
||||
"Error contains expected message"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function invalidSizeTypes({ IO }) {
|
||||
const contents = "Lorem ipsum";
|
||||
const { handle } = await registerFileStream(contents);
|
||||
|
||||
for (const size of [null, true, "1", [], {}]) {
|
||||
try {
|
||||
await IO.read({ handle, size });
|
||||
ok(false, "Read shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.startsWith(`size: integer value expected`),
|
||||
"Error contains expected message"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* import-globals-from ../head.js */
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/remote/test/browser/head.js",
|
||||
this
|
||||
);
|
||||
|
||||
const { streamRegistry } = ChromeUtils.import(
|
||||
"chrome://remote/content/domains/parent/IO.jsm"
|
||||
);
|
||||
|
||||
async function registerFileStream(contents, options = {}) {
|
||||
// Any file as registered with the stream registry will be automatically
|
||||
// deleted during the shutdown of Firefox.
|
||||
options.remove = false;
|
||||
|
||||
const { file, path } = await createFile(contents, options);
|
||||
const handle = streamRegistry.add(file);
|
||||
|
||||
return { handle, path };
|
||||
}
|
|
@ -7,6 +7,7 @@ BROWSER_CHROME_MANIFESTS += [
|
|||
"browser/browser.ini",
|
||||
"browser/emulation/browser.ini",
|
||||
"browser/input/browser.ini",
|
||||
"browser/io/browser.ini",
|
||||
"browser/network/browser.ini",
|
||||
"browser/page/browser.ini",
|
||||
"browser/runtime/browser.ini",
|
||||
|
|
Загрузка…
Ссылка в новой задаче