mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 08:15:31 +00:00
Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
This commit is contained in:
commit
3ca870b28b
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -837,21 +837,21 @@ name = "encoding_c"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_glue"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nserror 0.1.0",
|
||||
"nsstring 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1750,7 +1750,7 @@ name = "nsstring"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3256,7 +3256,7 @@ dependencies = [
|
||||
"checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
|
||||
"checksum ena 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dc8393b3c7352f94092497f6b52019643e493b6b890eb417cdb7c46117e621"
|
||||
"checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
|
||||
"checksum encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"
|
||||
"checksum encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "a69d152eaa438a291636c1971b0a370212165ca8a75759eb66818c5ce9b538f7"
|
||||
"checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
|
||||
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
|
||||
"checksum euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dbbf962bb6f877239a34491f2e0a12c6b824f389bc789eb90f1d70d4780b0727"
|
||||
|
@ -251,8 +251,13 @@ void DocAccessibleWrap::UpdateFocusPathBounds() {
|
||||
SessionAccessibility::GetInstanceFor(this)) {
|
||||
nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count());
|
||||
for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
|
||||
accessibles.AppendElement(
|
||||
static_cast<AccessibleWrap*>(iter.Data().get()));
|
||||
Accessible* accessible = iter.Data();
|
||||
if (!accessible || accessible->IsDefunct()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
|
||||
continue;
|
||||
}
|
||||
|
||||
accessibles.AppendElement(static_cast<AccessibleWrap*>(accessible));
|
||||
}
|
||||
|
||||
sessionAcc->UpdateCachedBounds(accessibles);
|
||||
|
@ -3688,13 +3688,26 @@ function getClosestNonBucketNode(item) {
|
||||
return getClosestNonBucketNode(parent);
|
||||
}
|
||||
|
||||
function getNonPrototypeParentGripValue(item) {
|
||||
function getParentGripNode(item) {
|
||||
const parentNode = getParent(item);
|
||||
if (!parentNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parentGripNode = getClosestGripNode(parentNode);
|
||||
return getClosestGripNode(parentNode);
|
||||
}
|
||||
|
||||
function getParentGripValue(item) {
|
||||
const parentGripNode = getParentGripNode(item);
|
||||
if (!parentGripNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getValue(parentGripNode);
|
||||
}
|
||||
|
||||
function getNonPrototypeParentGripValue(item) {
|
||||
const parentGripNode = getParentGripNode(item);
|
||||
if (!parentGripNode) {
|
||||
return null;
|
||||
}
|
||||
@ -3716,6 +3729,7 @@ module.exports = {
|
||||
getClosestGripNode,
|
||||
getClosestNonBucketNode,
|
||||
getParent,
|
||||
getParentGripValue,
|
||||
getNonPrototypeParentGripValue,
|
||||
getNumericalPropertiesCount,
|
||||
getValue,
|
||||
@ -6406,11 +6420,11 @@ function releaseActors(state, client) {
|
||||
}
|
||||
}
|
||||
|
||||
function invokeGetter(node, grip, getterName) {
|
||||
function invokeGetter(node, targetGrip, receiverId, getterName) {
|
||||
return async ({ dispatch, client, getState }) => {
|
||||
try {
|
||||
const objectClient = client.createObjectClient(grip);
|
||||
const result = await objectClient.getPropertyValue(getterName);
|
||||
const objectClient = client.createObjectClient(targetGrip);
|
||||
const result = await objectClient.getPropertyValue(getterName, receiverId);
|
||||
dispatch({
|
||||
type: "GETTER_INVOKED",
|
||||
data: {
|
||||
@ -7004,6 +7018,7 @@ const {
|
||||
nodeIsLongString,
|
||||
nodeHasFullText,
|
||||
nodeHasGetter,
|
||||
getParentGripValue,
|
||||
getNonPrototypeParentGripValue
|
||||
} = Utils.node;
|
||||
|
||||
@ -7078,10 +7093,11 @@ class ObjectInspectorItem extends Component {
|
||||
}
|
||||
|
||||
if (nodeHasGetter(item)) {
|
||||
const parentGrip = getNonPrototypeParentGripValue(item);
|
||||
if (parentGrip) {
|
||||
const targetGrip = getParentGripValue(item);
|
||||
const receiverGrip = getNonPrototypeParentGripValue(item);
|
||||
if (targetGrip && receiverGrip) {
|
||||
Object.assign(repProps, {
|
||||
onInvokeGetterButtonClick: () => this.props.invokeGetter(item, parentGrip, item.name)
|
||||
onInvokeGetterButtonClick: () => this.props.invokeGetter(item, targetGrip, receiverGrip.actor, item.name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +355,7 @@ skip-if = true # Bug 1438979
|
||||
[browser_webconsole_object_inspector_entries.js]
|
||||
[browser_webconsole_object_inspector_getters.js]
|
||||
[browser_webconsole_object_inspector_getters_prototype.js]
|
||||
[browser_webconsole_object_inspector_getters_shadowed.js]
|
||||
[browser_webconsole_object_inspector_key_sorting.js]
|
||||
[browser_webconsole_object_inspector_local_session_storage.js]
|
||||
[browser_webconsole_object_inspector_selected_text.js]
|
||||
|
@ -0,0 +1,79 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Check evaluating shadowed getters in the console.
|
||||
const TEST_URI = "data:text/html;charset=utf8,<h1>Object Inspector on Getters</h1>";
|
||||
|
||||
add_task(async function() {
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
const a = {
|
||||
getter: "[A]",
|
||||
__proto__: {
|
||||
get getter() {
|
||||
return "[B]";
|
||||
},
|
||||
__proto__: {
|
||||
get getter() {
|
||||
return "[C]";
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const b = {
|
||||
value: 1,
|
||||
get getter() {
|
||||
return `[A-${this.value}]`;
|
||||
},
|
||||
__proto__: {
|
||||
value: 2,
|
||||
get getter() {
|
||||
return `[B-${this.value}]`;
|
||||
},
|
||||
},
|
||||
};
|
||||
content.wrappedJSObject.console.log("oi-test", a, b);
|
||||
});
|
||||
|
||||
const node = await waitFor(() => findMessage(hud, "oi-test"));
|
||||
const [a, b] = node.querySelectorAll(".tree");
|
||||
|
||||
await testObject(a, [null, "[B]", "[C]"]);
|
||||
await testObject(b, ["[A-1]", "[B-1]"]);
|
||||
});
|
||||
|
||||
async function testObject(oi, values) {
|
||||
let node = oi.querySelector(".tree-node");
|
||||
for (const value of values) {
|
||||
await expand(node);
|
||||
if (value != null) {
|
||||
const getter = findObjectInspectorNodeChild(node, "getter");
|
||||
await invokeGetter(getter);
|
||||
ok(getter.textContent.includes(`getter: "${value}"`),
|
||||
`Getter now has the expected "${value}" content`);
|
||||
}
|
||||
node = findObjectInspectorNodeChild(node, "<prototype>");
|
||||
}
|
||||
}
|
||||
|
||||
function expand(node) {
|
||||
expandObjectInspectorNode(node);
|
||||
return waitFor(() => getObjectInspectorChildrenNodes(node).length > 0);
|
||||
}
|
||||
|
||||
function invokeGetter(node) {
|
||||
getObjectInspectorInvokeGetterButton(node).click();
|
||||
return waitFor(() => !getObjectInspectorInvokeGetterButton(node));
|
||||
}
|
||||
|
||||
function findObjectInspectorNodeChild(node, nodeLabel) {
|
||||
return getObjectInspectorChildrenNodes(node).find(child => {
|
||||
const label = child.querySelector(".object-label");
|
||||
return label && label.textContent === nodeLabel;
|
||||
});
|
||||
}
|
@ -511,13 +511,26 @@ const proto = {
|
||||
*
|
||||
* @param {string} name
|
||||
* The property we want the value of.
|
||||
* @param {string|null} receiverId
|
||||
* The actorId of the receiver to be used if the property is a getter.
|
||||
* If null or invalid, the receiver will be the referent.
|
||||
*/
|
||||
propertyValue: function(name) {
|
||||
propertyValue: function(name, receiverId) {
|
||||
if (!name) {
|
||||
return this.throwError("missingParameter", "no property name was specified");
|
||||
}
|
||||
|
||||
const value = this.obj.getProperty(name);
|
||||
let receiver;
|
||||
if (receiverId) {
|
||||
const receiverActor = this.conn.getActor(receiverId);
|
||||
if (receiverActor) {
|
||||
receiver = receiverActor.obj;
|
||||
}
|
||||
}
|
||||
|
||||
const value = receiver
|
||||
? this.obj.getProperty(name, receiver)
|
||||
: this.obj.getProperty(name);
|
||||
|
||||
return { value: this._buildCompletion(value) };
|
||||
},
|
||||
|
@ -37,17 +37,17 @@ async function test_object_grip(debuggee, threadClient) {
|
||||
});
|
||||
`,
|
||||
async objClient => {
|
||||
const obj1 = (await objClient.getPropertyValue("obj1")).value.return;
|
||||
const obj2 = (await objClient.getPropertyValue("obj2")).value.return;
|
||||
const obj1 = (await objClient.getPropertyValue("obj1", null)).value.return;
|
||||
const obj2 = (await objClient.getPropertyValue("obj2", null)).value.return;
|
||||
|
||||
const context = threadClient.pauseGrip(
|
||||
(await objClient.getPropertyValue("context")).value.return,
|
||||
(await objClient.getPropertyValue("context", null)).value.return,
|
||||
);
|
||||
const sum = threadClient.pauseGrip(
|
||||
(await objClient.getPropertyValue("sum")).value.return,
|
||||
(await objClient.getPropertyValue("sum", null)).value.return,
|
||||
);
|
||||
const error = threadClient.pauseGrip(
|
||||
(await objClient.getPropertyValue("error")).value.return,
|
||||
(await objClient.getPropertyValue("error", null)).value.return,
|
||||
);
|
||||
|
||||
assert_response(await context.apply(obj1, [obj1]), {
|
||||
|
@ -35,7 +35,7 @@ async function test_object_grip(debuggee, threadClient) {
|
||||
const objClient = threadClient.pauseGrip(obj);
|
||||
|
||||
const method = threadClient.pauseGrip(
|
||||
(await objClient.getPropertyValue("method")).value.return,
|
||||
(await objClient.getPropertyValue("method", null)).value.return,
|
||||
);
|
||||
|
||||
// Ensure that we actually paused at the `debugger;` line.
|
||||
|
@ -33,7 +33,7 @@ async function test_object_grip(debuggee, threadClient) {
|
||||
const objClient = threadClient.pauseGrip(obj);
|
||||
|
||||
const method = threadClient.pauseGrip(
|
||||
(await objClient.getPropertyValue("method")).value.return,
|
||||
(await objClient.getPropertyValue("method", null)).value.return,
|
||||
);
|
||||
|
||||
try {
|
||||
|
@ -99,7 +99,7 @@ async function test_object_grip(debuggee, threadClient) {
|
||||
};
|
||||
|
||||
for (const [key, expected] of Object.entries(expectedValues)) {
|
||||
const { value } = await objClient.getPropertyValue(key);
|
||||
const { value } = await objClient.getPropertyValue(key, null);
|
||||
|
||||
assert_completion(value, expected);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ async function test_object_grip(debuggee, threadClient) {
|
||||
Assert.equal(frame.where.line, 4);
|
||||
Assert.equal(frame.where.column, 8);
|
||||
}),
|
||||
objClient.getPropertyValue("prop"),
|
||||
objClient.getPropertyValue("prop", null),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
|
||||
});
|
||||
|
||||
add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
|
||||
debuggee.eval(function stopMe() {
|
||||
debugger;
|
||||
}.toString());
|
||||
|
||||
await test_object_grip(debuggee, threadClient);
|
||||
}));
|
||||
|
||||
async function test_object_grip(debuggee, threadClient) {
|
||||
eval_and_resume(
|
||||
debuggee,
|
||||
threadClient,
|
||||
`
|
||||
var obj = {
|
||||
get getter() {
|
||||
return objects.indexOf(this);
|
||||
},
|
||||
};
|
||||
var objects = [obj, {}, [], new Boolean(), new Number(), new String()];
|
||||
stopMe(...objects);
|
||||
`,
|
||||
async frame => {
|
||||
const grips = frame.arguments;
|
||||
const objClient = threadClient.pauseGrip(grips[0]);
|
||||
const classes = ["Object", "Object", "Array", "Boolean", "Number", "String"];
|
||||
for (const [i, grip] of grips.entries()) {
|
||||
Assert.equal(grip.class, classes[i]);
|
||||
await check_getter(objClient, grip.actor, i);
|
||||
}
|
||||
await check_getter(objClient, null, 0);
|
||||
await check_getter(objClient, "invalid receiver actorId", 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function eval_and_resume(debuggee, threadClient, code, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wait_for_pause(threadClient, callback).then(resolve, reject);
|
||||
|
||||
// This synchronously blocks until 'threadClient.resume()' above runs
|
||||
// because the 'paused' event runs everthing in a new event loop.
|
||||
debuggee.eval(code);
|
||||
});
|
||||
}
|
||||
|
||||
function wait_for_pause(threadClient, callback = () => {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
threadClient.addOneTimeListener("paused", function(event, packet) {
|
||||
(async () => {
|
||||
try {
|
||||
return await callback(packet.frame);
|
||||
} finally {
|
||||
await threadClient.resume();
|
||||
}
|
||||
})().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function check_getter(objClient, receiverId, expected) {
|
||||
const {value} = await objClient.getPropertyValue("getter", receiverId);
|
||||
Assert.equal(value.return, expected);
|
||||
}
|
@ -165,6 +165,7 @@ reason = bug 1104838
|
||||
[test_objectgrips-array-like-object.js]
|
||||
[test_objectgrips-property-value-01.js]
|
||||
[test_objectgrips-property-value-02.js]
|
||||
[test_objectgrips-property-value-03.js]
|
||||
[test_objectgrips-fn-apply-01.js]
|
||||
[test_objectgrips-fn-apply-02.js]
|
||||
[test_objectgrips-fn-apply-03.js]
|
||||
|
@ -189,11 +189,13 @@ ObjectClient.prototype = {
|
||||
* Request the value of the object's specified property.
|
||||
*
|
||||
* @param name string The name of the requested property.
|
||||
* @param receiverId string|null The actorId of the receiver to be used for getters.
|
||||
* @param onResponse function Called with the request's response.
|
||||
*/
|
||||
getPropertyValue: DebuggerClient.requester({
|
||||
type: "propertyValue",
|
||||
name: arg(0),
|
||||
receiverId: arg(1),
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -181,6 +181,7 @@ const objectSpec = generateActorSpec({
|
||||
propertyValue: {
|
||||
request: {
|
||||
name: Arg(0, "string"),
|
||||
receiverId: Arg(1, "nullable:string"),
|
||||
},
|
||||
response: RetVal("object.propertyValue"),
|
||||
},
|
||||
|
@ -151,7 +151,17 @@ class Animation : public DOMEventTargetHelper,
|
||||
|
||||
virtual void Tick();
|
||||
bool NeedsTicks() const {
|
||||
return Pending() || PlayState() == AnimationPlayState::Running;
|
||||
return Pending() ||
|
||||
(PlayState() == AnimationPlayState::Running &&
|
||||
// An animation with a zero playback rate doesn't need ticks even if
|
||||
// it is running since it effectively behaves as if it is paused.
|
||||
//
|
||||
// It's important we return false in this case since a zero playback
|
||||
// rate animation in the before or after phase that doesn't fill
|
||||
// won't be relevant and hence won't be returned by GetAnimations().
|
||||
// We don't want its timeline to keep it alive (which would happen
|
||||
// if we return true) since otherwise it will effectively be leaked.
|
||||
PlaybackRate() != 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,8 +24,7 @@ void TextEncoder::Encode(JSContext* aCx, JS::Handle<JSObject*> aObj,
|
||||
// in the future.
|
||||
// Uint8Array::Create takes uint32_t as the length.
|
||||
CheckedInt<uint32_t> bufLen(aString.Length());
|
||||
bufLen *= 3;
|
||||
bufLen += 1; // plus one is part of the contract for ConvertUTF16toUTF8
|
||||
bufLen *= 3; // from the contract for ConvertUTF16toUTF8
|
||||
if (!bufLen.isValid()) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
|
@ -187,7 +187,9 @@ nsresult AudioSink::InitializeAudioStream(const PlaybackParams& aParams) {
|
||||
|
||||
// Set playback params before calling Start() so they can take effect
|
||||
// as soon as the 1st DataCallback of the AudioStream fires.
|
||||
mAudioStream->SetVolume(aParams.mVolume);
|
||||
if (aParams.mVolume) {
|
||||
mAudioStream->SetVolume(*aParams.mVolume);
|
||||
}
|
||||
mAudioStream->SetPlaybackRate(aParams.mPlaybackRate);
|
||||
mAudioStream->SetPreservesPitch(aParams.mPreservesPitch);
|
||||
return mAudioStream->Start();
|
||||
|
@ -29,7 +29,9 @@ const MediaSink::PlaybackParams& AudioSinkWrapper::GetPlaybackParams() const {
|
||||
void AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams) {
|
||||
AssertOwnerThread();
|
||||
if (mAudioSink) {
|
||||
mAudioSink->SetVolume(aParams.mVolume);
|
||||
if (aParams.mVolume) {
|
||||
mAudioSink->SetVolume(*aParams.mVolume);
|
||||
}
|
||||
mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
|
||||
mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
|
||||
}
|
||||
@ -96,7 +98,7 @@ bool AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const {
|
||||
|
||||
void AudioSinkWrapper::SetVolume(double aVolume) {
|
||||
AssertOwnerThread();
|
||||
mParams.mVolume = aVolume;
|
||||
mParams.mVolume = Some(aVolume);
|
||||
if (mAudioSink) {
|
||||
mAudioSink->SetVolume(aVolume);
|
||||
}
|
||||
@ -196,6 +198,10 @@ void AudioSinkWrapper::Stop() {
|
||||
|
||||
if (mAudioSink) {
|
||||
mAudioSinkEndedPromise.DisconnectIfExists();
|
||||
// Reset volume to signal that it should
|
||||
// not be updated, in case the volume
|
||||
// has been changed outside MediaElement.
|
||||
mParams.mVolume.reset();
|
||||
mAudioSink->Shutdown();
|
||||
mAudioSink = nullptr;
|
||||
mEndedPromise = nullptr;
|
||||
|
@ -479,7 +479,7 @@ void DecodedStream::SetPlaying(bool aPlaying) {
|
||||
|
||||
void DecodedStream::SetVolume(double aVolume) {
|
||||
AssertOwnerThread();
|
||||
mParams.mVolume = aVolume;
|
||||
mParams.mVolume = Some(aVolume);
|
||||
}
|
||||
|
||||
void DecodedStream::SetPlaybackRate(double aPlaybackRate) {
|
||||
@ -730,7 +730,8 @@ void DecodedStream::SendData() {
|
||||
return;
|
||||
}
|
||||
|
||||
SendAudio(mParams.mVolume, mSameOrigin, mPrincipalHandle);
|
||||
MOZ_ASSERT(mParams.mVolume.isSome(), "Volume should exist at that point");
|
||||
SendAudio(mParams.mVolume.value(), mSameOrigin, mPrincipalHandle);
|
||||
SendVideo(mSameOrigin, mPrincipalHandle);
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,8 @@ class MediaSink {
|
||||
|
||||
struct PlaybackParams {
|
||||
PlaybackParams()
|
||||
: mVolume(1.0), mPlaybackRate(1.0), mPreservesPitch(true) {}
|
||||
double mVolume;
|
||||
: mVolume(Some(1.0)), mPlaybackRate(1.0), mPreservesPitch(true) {}
|
||||
Maybe<double> mVolume;
|
||||
double mPlaybackRate;
|
||||
bool mPreservesPitch;
|
||||
RefPtr<AudioDeviceInfo> mSink;
|
||||
|
@ -606,12 +606,11 @@ nsresult MediaEngineWebRTCMicrophoneSource::Stop(
|
||||
|
||||
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
|
||||
NS_DispatchToMainThread(
|
||||
media::NewRunnableFrom([that, stream = mStream, track = mTrackID]() {
|
||||
media::NewRunnableFrom([that, stream = mStream]() {
|
||||
if (stream->IsDestroyed()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
stream->SetPullingEnabled(track, false);
|
||||
stream->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
|
||||
that->mInputProcessing, StartStopMessage::Stop));
|
||||
CubebUtils::AudioDeviceID deviceID = that->mDeviceInfo->DeviceID();
|
||||
|
@ -9,14 +9,33 @@
|
||||
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function addMouseEventListeners(aTarget) {
|
||||
aTarget.addEventListener("mousemove", recordEvent, true);
|
||||
aTarget.addEventListener("mouseover", recordEvent, true);
|
||||
aTarget.addEventListener("mouseenter", recordEvent, true);
|
||||
aTarget.addEventListener("mouseout", recordEvent, true);
|
||||
aTarget.addEventListener("mouseleave", recordEvent, true);
|
||||
}
|
||||
|
||||
function removeMouseEventListeners(aTarget) {
|
||||
aTarget.removeEventListener("mousemove", recordEvent, true);
|
||||
aTarget.removeEventListener("mouseover", recordEvent, true);
|
||||
aTarget.removeEventListener("mouseenter", recordEvent, true);
|
||||
aTarget.removeEventListener("mouseout", recordEvent, true);
|
||||
aTarget.removeEventListener("mouseleave", recordEvent, true);
|
||||
}
|
||||
|
||||
function longPressLink() {
|
||||
synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
|
||||
let target = document.getElementById("b");
|
||||
addMouseEventListeners(target);
|
||||
synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
|
||||
dump("Finished synthesizing touch-start, waiting for events...\n");
|
||||
});
|
||||
}
|
||||
|
||||
var eventsFired = 0;
|
||||
function recordEvent(e) {
|
||||
let target = document.getElementById("b");
|
||||
if (getPlatform() == "windows") {
|
||||
// On Windows we get a mouselongtap event once the long-tap has been detected
|
||||
// by APZ, and that's what we use as the trigger to lift the finger. That then
|
||||
@ -28,12 +47,16 @@ function recordEvent(e) {
|
||||
synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
|
||||
break;
|
||||
case 2: is(e.type, "touchend", "Got a touchend"); break;
|
||||
case 3: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
|
||||
case 3: is(e.type, "mouseover", "Got a mouseover"); break;
|
||||
case 4: is(e.type, "mouseenter", "Got a mouseenter"); break;
|
||||
case 5: is(e.type, "mousemove", "Got a mousemove"); break;
|
||||
case 6: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
|
||||
default: ok(false, "Got an unexpected event of type " + e.type); break;
|
||||
}
|
||||
eventsFired++;
|
||||
|
||||
if (eventsFired == 4) {
|
||||
if (eventsFired == 7) {
|
||||
removeMouseEventListeners(target);
|
||||
dump("Finished waiting for events, doing an APZ flush to see if any more unexpected events come through...\n");
|
||||
flushApzRepaints(function() {
|
||||
dump("Done APZ flush, ending test...\n");
|
||||
@ -46,14 +69,18 @@ function recordEvent(e) {
|
||||
// event at all, and instead get a touchcancel.
|
||||
switch (eventsFired) {
|
||||
case 0: is(e.type, "touchstart", "Got a touchstart"); break;
|
||||
case 1: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
|
||||
case 2: is(e.type, "touchcancel", "Got a touchcancel"); break;
|
||||
case 1: is(e.type, "mouseover", "Got a mouseover"); break;
|
||||
case 2: is(e.type, "mouseenter", "Got a mouseenter"); break;
|
||||
case 3: is(e.type, "mousemove", "Got a mousemove"); break;
|
||||
case 4: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
|
||||
case 5: is(e.type, "touchcancel", "Got a touchcancel"); break;
|
||||
default: ok(false, "Got an unexpected event of type " + e.type); break;
|
||||
}
|
||||
eventsFired++;
|
||||
|
||||
if (eventsFired == 3) {
|
||||
synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
|
||||
if (eventsFired == 6) {
|
||||
removeMouseEventListeners(target);
|
||||
synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
|
||||
dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
|
||||
flushApzRepaints(function() {
|
||||
dump("Done APZ flush, ending test...\n");
|
||||
|
@ -215,6 +215,17 @@ bool APZEventState::FireContextmenuEvents(
|
||||
const nsCOMPtr<nsIPresShell>& aPresShell, const CSSPoint& aPoint,
|
||||
const CSSToLayoutDeviceScale& aScale, Modifiers aModifiers,
|
||||
const nsCOMPtr<nsIWidget>& aWidget) {
|
||||
// Synthesize mousemove event for allowing users to emulate to move mouse
|
||||
// cursor over the element. As a result, users can open submenu UI which
|
||||
// is opened when mouse cursor is moved over a link (i.e., it's a case that
|
||||
// users cannot stay in the page after tapping it). So, this improves
|
||||
// accessibility in websites which are designed for desktop.
|
||||
// Note that we don't need to check whether mousemove event is consumed or
|
||||
// not because Chrome also ignores the result.
|
||||
APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
eMouseMove, 0 /* time */, aPoint * aScale, aModifiers, 0 /* clickCount */,
|
||||
aWidget);
|
||||
|
||||
// Converting the modifiers to DOM format for the DispatchMouseEvent call
|
||||
// is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
|
||||
// just converts them back to widget format, but that API has many callers,
|
||||
|
@ -22,153 +22,3 @@ For a debug webrender build:
|
||||
Use a debug mozconfig (ac_add_options --enable-debug)
|
||||
You can also use an opt build but make webrender less optimized by putting opt-level=0 in the [profile.release] section of your toolkit/library/rust/Cargo.toml file
|
||||
See also https://groups.google.com/forum/#!topic/mozilla.dev.servo/MbeMcqqO1fs
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
What if you have to pull in an update to webrender itself? You have two options,
|
||||
listed below. Both options will give you a set of patches and the ability to do
|
||||
try pushes to verify the update. After that, continue with the steps below to
|
||||
actually land the update into the tree.
|
||||
|
||||
Option A:
|
||||
Use a script to do the update for you. This will usually work, if you satisfy
|
||||
all the assumptions the script is making. The script can be found at
|
||||
https://github.com/staktrace/wrupdater/blob/master/try-latest-webrender.sh
|
||||
and contains documentation on how to use it. Read the documentation carefully
|
||||
before trying to use it.
|
||||
|
||||
Option B:
|
||||
Do the update manually. This is a little more cumbersome but may be required
|
||||
if the script doesn't work or the repos are in a state that violates hidden
|
||||
assumptions in the script (e.g. if the webrender_bindings/Cargo.toml file is
|
||||
no longer in the format expected by the script). The steps to do this are,
|
||||
roughly:
|
||||
- Update your mozilla-central checkout to the latest code on mozilla-central.
|
||||
- Check out and update the webrender repo to the version you want
|
||||
- Copy over the webrender repo to gfx/wr. The easiest way to do this is
|
||||
simply delete gfx/wr, and use |cp -R| to copy it back, and then delete the
|
||||
gfx/wr/.git/ subfolder. Update the revision in
|
||||
gfx/webrender_bindings/revision.txt file with the git changeset hash.
|
||||
- If you need to modify webrender_bindings/Cargo.toml file, do so now. Changes
|
||||
at this step usually consist of:
|
||||
(a) Updating version numbers. Go through the version numbers of ALL the
|
||||
dependencies in the Cargo.toml file (webrender, euclid, etc.) and make
|
||||
sure the version numbers listed match what's in the new
|
||||
gfx/wr/webrender/Cargo.toml and gfx/wr/webrender_api/Cargo.toml files.
|
||||
(b) Turning on or off any new features that were added in upstream WR. This
|
||||
used to happen a lot but is pretty rare now.
|
||||
- Go to toolkit/library/rust and run |cargo update -p webrender -p webrender_api|.
|
||||
If it complains about version numbers of other crates not lining up, add those
|
||||
as well, e.g. |cargo update -p webrender -p webrender_api -p gleam -p euclid|.
|
||||
You may need to do this a few times until you get all the crates to make it
|
||||
happy.
|
||||
- Run the same cargo update command from the previous step in the
|
||||
toolkit/library/gtest/rust folder.
|
||||
- Commit your changes locally. You'll need to do this before the next step or
|
||||
it will complain.
|
||||
- At the top of the tree, run |mach vendor rust| to update the rust
|
||||
dependencies in third_party/rust.
|
||||
- Commit your changes locally.
|
||||
- Build and test. You may need to make changes in bindings.rs or on the C++
|
||||
side depending on what changed in webrender. This can potentially be quite
|
||||
tricky if you don't fully understand the API changes on the webrender side.
|
||||
Get help if you need it. For simplicity in bisecting, try to not use your
|
||||
new features yet, just get the build working with the minimal changes.
|
||||
- Commit any changes from the previous step, and do a try push to make sure
|
||||
everything is good. Generally we do two try pushes, one for builds and
|
||||
linux tests. This should be totally green. The other forces WR enabled on
|
||||
Windows and runs reftests, which currently fails. However if it fails with
|
||||
more than just regular reftest failures (e.g. it crashes or has an assertion
|
||||
failure) then that's potentially going to be a problem for Windows users
|
||||
running WebRender and will need investigation.
|
||||
- You now have an updated webrender, so you can land it or write gecko
|
||||
code against the new features.
|
||||
|
||||
Once you have followed either Option A or Option B and have a good update, you
|
||||
might want to land it in the tree. To do this:
|
||||
- Find the current wr-future-update bug, by going to https://bugzil.la/wr-future-update
|
||||
- Clone this bug (there is a little dropdown in the bottom right corner of the
|
||||
page which gives you an option to "Create a new bug ... as a clone of this bug").
|
||||
- This will take you to a bug entry page with some stuff prepopulated. Do NOT
|
||||
submit it yet, but make the following changes:
|
||||
(a) Modify the "Description" to remove the SECOND instance of the text "+++ This
|
||||
bug was initially created as a clone of ... +++". Keep the first instance
|
||||
as it points to the bug you just cloned, and keep the rest of the text unless
|
||||
you feel it needs changing.
|
||||
(b) Add wr-future-update into the "Alias" field
|
||||
(c) Clear the bugs in the "Depends on" field
|
||||
(d) For each bug in the "Blocks" field, except for 1311790 and 1386670, go
|
||||
to the bug and check the "See Also" link for the corresponding WR issue/PR,
|
||||
if any. If there is a WR issue that is not yet resolved in the update you
|
||||
are landing, leave the bug in the "Blocks" field of your clone. In a later
|
||||
step you will remove the dependency from the update you are landing. At
|
||||
end of this step the "Blocks" field should contain 1311790, 1386670, and
|
||||
any bugs tracking upstream WR issues that are not fixed in the update.
|
||||
(e) You still cannot submit the clone as a new bug, because you can't have two
|
||||
bugs in the system with the same alias. So hold on a sec.
|
||||
- Go back to the tab with the current wr-future-update bug, and click on the edit
|
||||
button. Make the following changes:
|
||||
(a) Assign the bug to yourself.
|
||||
(b) Clear the "Alias" field.
|
||||
(c) Remove bugs from the "Blocks" field that you kept in step (d), other than
|
||||
1311790 and 1386670. In other words, update the "Blocks" field so that it
|
||||
contains 1311790, 1386670, and any bugs that are actually fixed by the
|
||||
update.
|
||||
(d) Submit your changes to this bug.
|
||||
- Now you can submit your changes to the clone bug which will create a new
|
||||
wr-future-update bug.
|
||||
- Update your patch queue so that the patches are properly formatted with
|
||||
bug number, reviewer, etc. and push to MozReview. This is kind of important,
|
||||
because you want these patches to land on autoland rather than inbound. If it
|
||||
lands on inbound there's a high chance of it conflicting with the servo-vcs-sync
|
||||
bot that is regularly pushing to autoland, and then you'll only find out about
|
||||
it when the sheriff tries to do a merge and backs you out. If you push to
|
||||
autoland you're likely to find out about the problem at push time, when the
|
||||
patches won't rebase.
|
||||
|
||||
|
||||
Troubleshooting tips:
|
||||
|
||||
1. Note that when webrender is built as part of gecko, it may end up using slightly
|
||||
different versions of its dependencies than when it is built standalone from the
|
||||
webrender repo. The reason is that the Cargo.lock files in m-c and in the WR
|
||||
repo may reference different versions of the dependencies. Both builds will be
|
||||
compatible in terms of semantic versioning, but may produce different results -
|
||||
for example the standalone webrender might use euclid 0.10.4 while the
|
||||
one in gecko uses euclid 0.10.3. Although both choices are "valid" per
|
||||
the semantic versioning rules in webrender's Cargo.toml, the 0.2.3 may provide
|
||||
a bugfix that is needed for correct behaviour in webrender. If this is the case,
|
||||
the technically "correct" fix is to change the upstream webrender Cargo.toml
|
||||
file to require the correct version. Alternnatively, you can update the
|
||||
Cargo.lock files in m-c to pull in the new version. The way to do this is as
|
||||
follows:
|
||||
- Go to toolkit/library/rust and run |cargo update -p <package> --precise <version>|.
|
||||
Repeat this for as many libraries as you need to update. Run the same commands
|
||||
in toolkit/library/gtest/rust and js/src (ignore any errors about unmatched
|
||||
packages). Commit all the changes locally.
|
||||
- Run |mach vendor rust|, which will update the corresponding libraries in
|
||||
third_party/rust to the versions you specified.
|
||||
The reason we don't do this by default is to work around bug 1336528. Specifically,
|
||||
there is another crate in m-c called mozjs_sys which is built separately but uses
|
||||
the same folder to store its rust dependencies. If one of the libraries that is
|
||||
required by both mozjs_sys and webrender is updated without updating the other
|
||||
project's Cargo.lock file, that results in build bustage.
|
||||
This means that any time you do this sort of manual update of packages, you need
|
||||
to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
|
||||
the need to run the cargo update command in js/src as well. Hopefully this will
|
||||
be resolved soon.
|
||||
|
||||
2. Sometimes autoland tip has changed enough from mozilla-central (because of the
|
||||
servo vcs-sync-bot, which will sync servo into m-c and often re-vendor third-
|
||||
party rust dependencies) that trying to land an update based on mozilla-central
|
||||
will not work well. As in, you'll get conflicts in Cargo.lock files or in the
|
||||
third_party/rust directory. This is best handled by running your update steps
|
||||
on top of autoland tip rather than central. (The script-based update in option A
|
||||
has an env var you can set to do this). In theory you can get the same
|
||||
result by resolving the conflict manually but Cargo.lock files are usually not
|
||||
trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
|
||||
you can delete it and run |mach vendor rust| again to repopulate it.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The current WebRender revision can be found in gfx/webrender_bindings/revision.txt file.
|
||||
|
@ -1 +0,0 @@
|
||||
d3edc30cf95d3c96fd8308969b22062698a0f6ce
|
@ -1699,8 +1699,6 @@ pub extern "C" fn wr_api_capture(
|
||||
let _ = create_dir_all(&path);
|
||||
match File::create(path.join("wr.txt")) {
|
||||
Ok(mut file) => {
|
||||
let revision = include_bytes!("../revision.txt");
|
||||
file.write(revision).unwrap();
|
||||
// The Gecko HG revision is available at compile time
|
||||
if let Some(moz_revision) = option_env!("GECKO_HEAD_REV") {
|
||||
writeln!(file, "mozilla-central {}", moz_revision).unwrap();
|
||||
|
@ -1,6 +1,17 @@
|
||||
# WebRender
|
||||
GPU renderer for the Web content, used by Servo.
|
||||
|
||||
Note that the canonical home for this code is in gfx/wr folder of the
|
||||
mozilla-central repository at https://hg.mozilla.org/mozilla-central. The
|
||||
Github repository at https://github.com/servo/webrender should be considered
|
||||
a downstream mirror, although it contains additional metadata (such as Github
|
||||
wiki pages) that do not exist in mozilla-central. Pull requests against the
|
||||
Github repository are still being accepted, although once reviewed, they will
|
||||
be landed on mozilla-central first and then mirrored back. If you are familiar
|
||||
with the mozilla-central contribution workflow, filing bugs in
|
||||
[Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Graphics%3A%20WebRender)
|
||||
and submitting patches there would be preferred.
|
||||
|
||||
## Update as a Dependency
|
||||
After updating shaders in WebRender, go to servo and:
|
||||
|
||||
|
@ -2266,14 +2266,16 @@ PresShell::IntraLineMove(bool aForward, bool aExtend) {
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresShell::PageMove(bool aForward, bool aExtend) {
|
||||
nsIFrame* frame;
|
||||
nsIFrame* frame = nullptr;
|
||||
if (!aExtend) {
|
||||
frame = do_QueryFrame(GetScrollableFrameToScroll(nsIPresShell::eVertical));
|
||||
} else {
|
||||
frame = mSelection->GetFrameToPageSelect();
|
||||
// If there is no scrollable frame, get the frame to move caret instead.
|
||||
}
|
||||
if (!frame) {
|
||||
return NS_OK;
|
||||
frame = mSelection->GetFrameToPageSelect();
|
||||
if (!frame) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
RefPtr<nsFrameSelection> frameSelection = mSelection;
|
||||
frameSelection->CommonPageMove(aForward, aExtend, frame);
|
||||
|
@ -151,8 +151,6 @@ skip-if = e10s # bug 1020135, nested oop iframes not supported
|
||||
support-files = bug921928_event_target_iframe_apps_oop.html
|
||||
[test_event_target_radius.html]
|
||||
skip-if = toolkit == 'android' # Bug 1355836
|
||||
[test_expanding_selection_per_page.html]
|
||||
support-files = window_empty_document.html
|
||||
[test_flush_on_paint.html]
|
||||
skip-if = true # Bug 688128
|
||||
[test_frame_reconstruction_for_pseudo_elements.html]
|
||||
@ -163,6 +161,8 @@ support-files =
|
||||
file_getBoxQuads_convertPointRectQuad_frame1.html
|
||||
file_getBoxQuads_convertPointRectQuad_frame2.html
|
||||
[test_getClientRects_emptytext.html]
|
||||
[test_moving_and_expanding_selection_per_page.html]
|
||||
support-files = window_empty_document.html
|
||||
[test_mozPaintCount.html]
|
||||
skip-if = toolkit == 'android' # android: Requires plugin support
|
||||
[test_preserve3d_sorting_hit_testing.html]
|
||||
|
@ -1,311 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for expanding selection per page</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(() => {
|
||||
open("window_empty_document.html", "_blank", "width=500,height=500");
|
||||
});
|
||||
|
||||
async function doTests(aWindow) {
|
||||
const IS_WIN = navigator.platform.includes("Win");
|
||||
// On macOS and Linux, Shift + PageUp/PageDown requires native event to
|
||||
// resolve default action of PageDown and PageUp. Although macOS widget has
|
||||
// nsIWidget::AttachNativeKeyEvent(), we cannot use synthesizeKey() for the
|
||||
// following tests. So, use nsISelectionController.pageMove() instead on
|
||||
// non-Windows platforms.
|
||||
const kUseKeyboardEvent = IS_WIN;
|
||||
let selectionController;
|
||||
if (!kUseKeyboardEvent) {
|
||||
selectionController = SpecialPowers.wrap(aWindow)
|
||||
.docShell
|
||||
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
.getInterface(SpecialPowers.Ci.nsISelectionDisplay)
|
||||
.QueryInterface(SpecialPowers.Ci.nsISelectionController);
|
||||
}
|
||||
// On Windows, per-page selection to start or end expands selection to same
|
||||
// column of first or last line. On the other platforms, it expands selection
|
||||
// to start or end of first or last line.
|
||||
const kSelectToStartOrEnd = !IS_WIN;
|
||||
|
||||
await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]});
|
||||
await SimpleTest.promiseFocus(aWindow);
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.getAttribute("id") !== null) {
|
||||
return `${aElement.tagName.toLowerCase()}#${aElement.getAttribute("id")}`;
|
||||
}
|
||||
if (aElement.tagName === "BR") {
|
||||
return `${getElementDescription(aElement.previousSibling)} + br`;
|
||||
}
|
||||
return aElement.tagName.toLowerCase();
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node in ${getElementDescription(aNode.parentElement)}`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(aNode);
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
function doSelectPageDown() {
|
||||
if (kUseKeyboardEvent) {
|
||||
synthesizeKey("KEY_PageDown", {shiftKey: true}, aWindow);
|
||||
} else {
|
||||
selectionController.pageMove(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
function doSelectPageUp() {
|
||||
if (kUseKeyboardEvent) {
|
||||
synthesizeKey("KEY_PageUp", {shiftKey: true}, aWindow);
|
||||
} else {
|
||||
selectionController.pageMove(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
let doc = aWindow.document;
|
||||
let body = doc.body;
|
||||
let selection = doc.getSelection();
|
||||
let container;
|
||||
|
||||
body.innerHTML = '<span id="s1">first line</span><br>' +
|
||||
'<span id="s2">second line</span><br>' +
|
||||
'<span id="s3">last line</span>';
|
||||
container = doc.documentElement;
|
||||
|
||||
let description = "Expanding selection to forward in non-scrollable body: ";
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s1").firstChild, 3);
|
||||
doSelectPageDown();
|
||||
is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
|
||||
let range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s1").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = "Expanding selection to backward in non-scrollable body: ";
|
||||
selection.collapse(doc.getElementById("s3").firstChild, 3);
|
||||
doSelectPageUp();
|
||||
is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s1").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = "Expanding selection to forward in scrollable area in the body: ";
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doSelectPageDown();
|
||||
isnot(container.scrollTop, 0, description + "should be scrolled down");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
|
||||
|
||||
description = "Expanding selection to backward in scrollable area in the body: ";
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
let previousScrollTop = container.scrollTop;
|
||||
doSelectPageUp();
|
||||
ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the 3rd line's 3rd insertion point`);
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = "Expanding selection to forward in scrollable editable div in the body: ";
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doSelectPageDown();
|
||||
isnot(container.scrollTop, 0, description + "should be scrolled down");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
|
||||
|
||||
description = "Expanding selection to backward in scrollable editable div in the body: ";
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
previousScrollTop = container.scrollTop;
|
||||
doSelectPageUp();
|
||||
ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the 3rd line's 3rd insertion point`);
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable>' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = "Expanding selection to forward in non-scrollable editable div in the body: ";
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doSelectPageDown();
|
||||
is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = "Expanding selection to backward in non-scrollable editable div in the body: ";
|
||||
selection.collapse(doc.getElementById("s5").firstChild, 3);
|
||||
doSelectPageUp();
|
||||
is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable>' +
|
||||
'<span id="s2">first editable line</span><br>' +
|
||||
'<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s3">first line</span><br>' +
|
||||
'<span id="s4">second line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s5">last editable line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d2");
|
||||
|
||||
description = "Expanding selection to forward in scrollable div (but not scrollable along y-axis) in the editable div: ";
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s3").firstChild, 3);
|
||||
doSelectPageDown();
|
||||
is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last editable line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last editable line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = "Expanding selection to backward in scrollable div (but not scrollable along y-axis) in the editable div: ";
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
doSelectPageUp();
|
||||
is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first editable line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first editable line's 3rd insertion point`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</html>
|
@ -0,0 +1,357 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for expanding selection per page</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(() => {
|
||||
open("window_empty_document.html", "_blank", "width=500,height=500");
|
||||
});
|
||||
|
||||
async function doTests(aWindow) {
|
||||
const IS_WIN = navigator.platform.includes("Win");
|
||||
// On macOS and Linux, Shift + PageUp/PageDown requires native event to
|
||||
// resolve default action of PageDown and PageUp. Although macOS widget has
|
||||
// nsIWidget::AttachNativeKeyEvent(), we cannot use synthesizeKey() for the
|
||||
// following tests. So, use nsISelectionController.pageMove() instead on
|
||||
// non-Windows platforms.
|
||||
const kUseKeyboardEvent = IS_WIN;
|
||||
let selectionController = SpecialPowers.wrap(aWindow)
|
||||
.docShell
|
||||
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
.getInterface(SpecialPowers.Ci.nsISelectionDisplay)
|
||||
.QueryInterface(SpecialPowers.Ci.nsISelectionController);
|
||||
// On Windows, per-page selection to start or end expands selection to same
|
||||
// column of first or last line. On the other platforms, it expands selection
|
||||
// to start or end of first or last line.
|
||||
const kSelectToStartOrEnd = !IS_WIN;
|
||||
|
||||
await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]});
|
||||
await SimpleTest.promiseFocus(aWindow);
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.getAttribute("id") !== null) {
|
||||
return `${aElement.tagName.toLowerCase()}#${aElement.getAttribute("id")}`;
|
||||
}
|
||||
if (aElement.tagName === "BR") {
|
||||
return `${getElementDescription(aElement.previousSibling)} + br`;
|
||||
}
|
||||
return aElement.tagName.toLowerCase();
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node in ${getElementDescription(aNode.parentElement)}`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(aNode);
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
function doTest(aExpandSelection) {
|
||||
// Note that when neither editor has focus nor in caret mode, key navigation
|
||||
// does not call nsISelectionController::PageMove(). Therefore, in such
|
||||
// cases, you need to call doPageDown() and doPageUp() with setting true
|
||||
// to aUseSelectionController.
|
||||
function doPageDown(aUseSelectionController) {
|
||||
if (kUseKeyboardEvent && !aUseSelectionController) {
|
||||
synthesizeKey("KEY_PageDown", {shiftKey: aExpandSelection}, aWindow);
|
||||
} else {
|
||||
selectionController.pageMove(true, aExpandSelection);
|
||||
}
|
||||
}
|
||||
|
||||
function doPageUp(aUseSelectionController) {
|
||||
if (kUseKeyboardEvent && !aUseSelectionController) {
|
||||
synthesizeKey("KEY_PageUp", {shiftKey: aExpandSelection}, aWindow);
|
||||
} else {
|
||||
selectionController.pageMove(false, aExpandSelection);
|
||||
}
|
||||
}
|
||||
|
||||
let doc = aWindow.document;
|
||||
let body = doc.body;
|
||||
let selection = doc.getSelection();
|
||||
let container;
|
||||
|
||||
body.innerHTML = '<span id="s1">first line</span><br>' +
|
||||
'<span id="s2">second line</span><br>' +
|
||||
'<span id="s3">last line</span>';
|
||||
container = doc.documentElement;
|
||||
|
||||
let description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable body: `;
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s1").firstChild, 3);
|
||||
doPageDown(!aExpandSelection);
|
||||
is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
|
||||
let range = selection.getRangeAt(0);
|
||||
if (aExpandSelection) {
|
||||
is(range.startContainer, doc.getElementById("s1").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable body: `;
|
||||
selection.collapse(doc.getElementById("s3").firstChild, 3);
|
||||
doPageUp(!aExpandSelection);
|
||||
is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s1").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
}
|
||||
if (aExpandSelection) {
|
||||
is(range.endContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable area in the body: `;
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doPageDown(!aExpandSelection);
|
||||
isnot(container.scrollTop, 0, description + "should be scrolled down");
|
||||
range = selection.getRangeAt(0);
|
||||
if (aExpandSelection) {
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable area in the body: `;
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
let previousScrollTop = container.scrollTop;
|
||||
doPageUp(!aExpandSelection);
|
||||
ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
if (aExpandSelection) {
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the 3rd line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable editable div in the body: `;
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doPageDown();
|
||||
isnot(container.scrollTop, 0, description + "should be scrolled down");
|
||||
range = selection.getRangeAt(0);
|
||||
if (aExpandSelection) {
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable editable div in the body: `;
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
previousScrollTop = container.scrollTop;
|
||||
doPageUp();
|
||||
ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
if (aExpandSelection) {
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the 3rd line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable>' +
|
||||
'<span id="s2">first line</span><br>' +
|
||||
'<span id="s3">second line</span><br>' +
|
||||
'<span id="s4">third line</span><br>' +
|
||||
'<span id="s5">last line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d1");
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable editable div in the body: `;
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s2").firstChild, 3);
|
||||
doPageDown();
|
||||
is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
if (aExpandSelection) {
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable editable div in the body: `;
|
||||
selection.collapse(doc.getElementById("s5").firstChild, 3);
|
||||
doPageUp();
|
||||
is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first line's 3rd insertion point`);
|
||||
}
|
||||
if (aExpandSelection) {
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
|
||||
body.innerHTML = '<span id="s1">first line in the body</span>' +
|
||||
'<div id="d1" contenteditable>' +
|
||||
'<span id="s2">first editable line</span><br>' +
|
||||
'<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
|
||||
'<span id="s3">first line</span><br>' +
|
||||
'<span id="s4">second line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s5">last editable line</span>' +
|
||||
"</div>" +
|
||||
'<span id="s6">last line in the body</span>';
|
||||
container = doc.getElementById("d2");
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable div (but not scrollable along y-axis) in the editable div: `;
|
||||
is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
|
||||
selection.collapse(doc.getElementById("s3").firstChild, 3);
|
||||
doPageDown();
|
||||
is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
if (aExpandSelection) {
|
||||
is(range.startContainer, doc.getElementById("s3").firstChild,
|
||||
`${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
is(range.startOffset, 3,
|
||||
`${description} selection should be expanded from the first line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
is(range.endContainer, doc.getElementById("s5").firstChild,
|
||||
`${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.endOffset, range.endContainer.length,
|
||||
`${description} selection should be expanded to end of the last editable line`);
|
||||
} else {
|
||||
isfuzzy(range.endOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the last editable line's 3rd insertion point`);
|
||||
}
|
||||
|
||||
description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable div (but not scrollable along y-axis) in the editable div: `;
|
||||
selection.collapse(doc.getElementById("s4").firstChild, 3);
|
||||
doPageUp();
|
||||
is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
|
||||
range = selection.getRangeAt(0);
|
||||
is(range.startContainer, doc.getElementById("s2").firstChild,
|
||||
`${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
|
||||
if (kSelectToStartOrEnd) {
|
||||
is(range.startOffset, 0,
|
||||
`${description} selection should be expanded to start of the first editable line`);
|
||||
} else {
|
||||
isfuzzy(range.startOffset, 3, 2,
|
||||
`${description} selection should be expanded to around the first editable line's 3rd insertion point`);
|
||||
}
|
||||
if (aExpandSelection) {
|
||||
is(range.endContainer, doc.getElementById("s4").firstChild,
|
||||
`${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
|
||||
is(range.endOffset, 3,
|
||||
`${description} selection should be expanded from the last line's 3rd insertion point`);
|
||||
} else {
|
||||
ok(range.collapsed, `${description} selection should be collapsed`);
|
||||
}
|
||||
}
|
||||
|
||||
doTest(false);
|
||||
doTest(true);
|
||||
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</html>
|
@ -1647,7 +1647,7 @@ class StaticAnalysis(MachCommandBase):
|
||||
"""Utilities for running C++ static analysis checks and format."""
|
||||
|
||||
# List of file extension to consider (should start with dot)
|
||||
_format_include_extensions = ('.cpp', '.c', '.cc', '.h', '.java')
|
||||
_format_include_extensions = ('.cpp', '.c', '.cc', '.h')
|
||||
# File contaning all paths to exclude from formatting
|
||||
_format_ignore_file = '.clang-format-ignore'
|
||||
|
||||
|
@ -13,6 +13,8 @@ const {
|
||||
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
|
||||
const {
|
||||
MessageManagerDestroyedPromise,
|
||||
waitForEvent,
|
||||
waitForObserverTopic,
|
||||
} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["browser", "Context", "WindowState"];
|
||||
@ -70,11 +72,11 @@ this.Context = Context;
|
||||
*/
|
||||
browser.getBrowserForTab = function(tab) {
|
||||
// Fennec
|
||||
if ("browser" in tab) {
|
||||
if (tab && "browser" in tab) {
|
||||
return tab.browser;
|
||||
|
||||
// Firefox
|
||||
} else if ("linkedBrowser" in tab) {
|
||||
} else if (tab && "linkedBrowser" in tab) {
|
||||
return tab.linkedBrowser;
|
||||
}
|
||||
|
||||
@ -287,17 +289,58 @@ browser.Context = class {
|
||||
* A promise which is resolved when the current window has been closed.
|
||||
*/
|
||||
closeWindow() {
|
||||
return new Promise(resolve => {
|
||||
// Wait for the window message manager to be destroyed
|
||||
let destroyed = new MessageManagerDestroyedPromise(
|
||||
this.window.messageManager);
|
||||
let destroyed = new MessageManagerDestroyedPromise(
|
||||
this.window.messageManager);
|
||||
let unloaded = waitForEvent(this.window, "unload");
|
||||
|
||||
this.window.addEventListener("unload", async () => {
|
||||
await destroyed;
|
||||
resolve();
|
||||
}, {once: true});
|
||||
this.window.close();
|
||||
});
|
||||
this.window.close();
|
||||
|
||||
return Promise.all([destroyed, unloaded]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new browser window.
|
||||
*
|
||||
* @return {Promise}
|
||||
* A promise resolving to the newly created chrome window.
|
||||
*/
|
||||
async openBrowserWindow(focus = false) {
|
||||
switch (this.driver.appName) {
|
||||
case "firefox":
|
||||
// Open new browser window, and wait until it is fully loaded.
|
||||
// Also wait for the window to be focused and activated to prevent a
|
||||
// race condition when promptly focusing to the original window again.
|
||||
let win = this.window.OpenBrowserWindow();
|
||||
|
||||
let activated = waitForEvent(win, "activate");
|
||||
let focused = waitForEvent(win, "focus", {capture: true});
|
||||
let startup = waitForObserverTopic("browser-delayed-startup-finished",
|
||||
subject => subject == win);
|
||||
|
||||
// Bug 1509380 - Missing focus/activate event when Firefox is not
|
||||
// the top-most application. As such wait for the next tick, and
|
||||
// manually focus the newly opened window.
|
||||
win.setTimeout(() => win.focus(), 0);
|
||||
|
||||
await Promise.all([activated, focused, startup]);
|
||||
|
||||
if (!focus) {
|
||||
// The new window shouldn't get focused. As such set the
|
||||
// focus back to the currently selected window.
|
||||
activated = waitForEvent(this.window, "activate");
|
||||
focused = waitForEvent(this.window, "focus", {capture: true});
|
||||
|
||||
this.window.focus();
|
||||
|
||||
await Promise.all([activated, focused]);
|
||||
}
|
||||
|
||||
return win;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationError(
|
||||
`openWindow() not supported in ${this.driver.appName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,40 +362,61 @@ browser.Context = class {
|
||||
return this.closeWindow();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Wait for the browser message manager to be destroyed
|
||||
let browserDetached = async () => {
|
||||
await new MessageManagerDestroyedPromise(this.messageManager);
|
||||
resolve();
|
||||
};
|
||||
let destroyed = new MessageManagerDestroyedPromise(this.messageManager);
|
||||
let tabClosed;
|
||||
|
||||
if (this.tabBrowser.closeTab) {
|
||||
switch (this.driver.appName) {
|
||||
case "fennec":
|
||||
// Fennec
|
||||
this.tabBrowser.deck.addEventListener(
|
||||
"TabClose", browserDetached, {once: true});
|
||||
tabClosed = waitForEvent(this.tabBrowser.deck, "TabClose");
|
||||
this.tabBrowser.closeTab(this.tab);
|
||||
break;
|
||||
|
||||
} else if (this.tabBrowser.removeTab) {
|
||||
// Firefox
|
||||
this.tab.addEventListener(
|
||||
"TabClose", browserDetached, {once: true});
|
||||
case "firefox":
|
||||
tabClosed = waitForEvent(this.tab, "TabClose");
|
||||
this.tabBrowser.removeTab(this.tab);
|
||||
break;
|
||||
|
||||
} else {
|
||||
reject(new UnsupportedOperationError(
|
||||
`closeTab() not supported in ${this.driver.appName}`));
|
||||
}
|
||||
});
|
||||
default:
|
||||
throw new UnsupportedOperationError(
|
||||
`closeTab() not supported in ${this.driver.appName}`);
|
||||
}
|
||||
|
||||
return Promise.all([destroyed, tabClosed]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a tab with given URI.
|
||||
*
|
||||
* @param {string} uri
|
||||
* URI to open.
|
||||
* Open a new tab in the currently selected chrome window.
|
||||
*/
|
||||
addTab(uri) {
|
||||
return this.tabBrowser.addTab(uri, true);
|
||||
async openTab(focus = false) {
|
||||
let tab = null;
|
||||
let tabOpened = waitForEvent(this.window, "TabOpen");
|
||||
|
||||
switch (this.driver.appName) {
|
||||
case "fennec":
|
||||
tab = this.tabBrowser.addTab(null, {selected: focus});
|
||||
break;
|
||||
|
||||
case "firefox":
|
||||
this.window.BrowserOpenTab();
|
||||
tab = this.tabBrowser.selectedTab;
|
||||
|
||||
// The new tab is always selected by default. If focus is not wanted,
|
||||
// the previously tab needs to be selected again.
|
||||
if (!focus) {
|
||||
this.tabBrowser.selectedTab = this.tab;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationError(
|
||||
`openTab() not supported in ${this.driver.appName}`);
|
||||
}
|
||||
|
||||
await tabOpened;
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -386,16 +450,18 @@ browser.Context = class {
|
||||
this.tab = this.tabBrowser.tabs[index];
|
||||
|
||||
if (focus) {
|
||||
if (this.tabBrowser.selectTab) {
|
||||
// Fennec
|
||||
this.tabBrowser.selectTab(this.tab);
|
||||
switch (this.driver.appName) {
|
||||
case "fennec":
|
||||
this.tabBrowser.selectTab(this.tab);
|
||||
break;
|
||||
|
||||
} else if ("selectedTab" in this.tabBrowser) {
|
||||
// Firefox
|
||||
this.tabBrowser.selectedTab = this.tab;
|
||||
case "firefox":
|
||||
this.tabBrowser.selectedTab = this.tab;
|
||||
break;
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationError("switchToTab() not supported");
|
||||
default:
|
||||
throw new UnsupportedOperationError(
|
||||
`switchToTab() not supported in ${this.driver.appName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1428,6 +1428,20 @@ class Marionette(object):
|
||||
return self._send_message("WebDriver:GetPageSource",
|
||||
key="value")
|
||||
|
||||
def open(self, type=None, focus=False):
|
||||
"""Open a new window, or tab based on the specified context type.
|
||||
|
||||
If no context type is given the application will choose the best
|
||||
option based on tab and window support.
|
||||
|
||||
:param type: Type of window to be opened. Can be one of "tab" or "window"
|
||||
:param focus: If true, the opened window will be focused
|
||||
|
||||
:returns: Dict with new window handle, and type of opened window
|
||||
"""
|
||||
body = {"type": type, "focus": focus}
|
||||
return self._send_message("WebDriver:NewWindow", body)
|
||||
|
||||
def close(self):
|
||||
"""Close the current window, ending the session if it's the last
|
||||
window currently open.
|
||||
|
@ -3,6 +3,8 @@ sync module
|
||||
|
||||
Provides an assortment of synchronisation primitives.
|
||||
|
||||
.. js:autofunction:: executeSoon
|
||||
|
||||
.. js:autoclass:: MessageManagerDestroyedPromise
|
||||
:members:
|
||||
|
||||
@ -14,3 +16,9 @@ Provides an assortment of synchronisation primitives.
|
||||
|
||||
.. js:autoclass:: TimedPromise
|
||||
:members:
|
||||
|
||||
.. js:autofunction:: waitForEvent
|
||||
|
||||
.. js:autofunction:: waitForMessage
|
||||
|
||||
.. js:autofunction:: waitForObserverTopic
|
||||
|
@ -62,6 +62,8 @@ const {
|
||||
IdlePromise,
|
||||
PollPromise,
|
||||
TimedPromise,
|
||||
waitForEvent,
|
||||
waitForObserverTopic,
|
||||
} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
|
||||
@ -106,13 +108,12 @@ const globalMessageManager = Services.mm;
|
||||
*
|
||||
* @class GeckoDriver
|
||||
*
|
||||
* @param {string} appId
|
||||
* Unique identifier of the application.
|
||||
* @param {MarionetteServer} server
|
||||
* The instance of Marionette server.
|
||||
*/
|
||||
this.GeckoDriver = function(appId, server) {
|
||||
this.appId = appId;
|
||||
this.GeckoDriver = function(server) {
|
||||
this.appId = Services.appinfo.ID;
|
||||
this.appName = Services.appinfo.name.toLowerCase();
|
||||
this._server = server;
|
||||
|
||||
this.sessionID = null;
|
||||
@ -1292,6 +1293,7 @@ GeckoDriver.prototype.getIdForBrowser = function(browser) {
|
||||
if (browser === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let permKey = browser.permanentKey;
|
||||
if (this._browserIds.has(permKey)) {
|
||||
return this._browserIds.get(permKey);
|
||||
@ -2707,6 +2709,66 @@ GeckoDriver.prototype.deleteCookie = async function(cmd) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a new top-level browsing context.
|
||||
*
|
||||
* @param {string=} type
|
||||
* Optional type of the new top-level browsing context. Can be one of
|
||||
* `tab` or `window`. Defaults to `tab`.
|
||||
* @param {boolean=} focus
|
||||
* Optional flag if the new top-level browsing context should be opened
|
||||
* in foreground (focused) or background (not focused). Defaults to false.
|
||||
*
|
||||
* @return {Object.<string, string>}
|
||||
* Handle and type of the new browsing context.
|
||||
*/
|
||||
GeckoDriver.prototype.newWindow = async function(cmd) {
|
||||
assert.open(this.getCurrentWindow(Context.Content));
|
||||
await this._handleUserPrompts();
|
||||
|
||||
let focus = false;
|
||||
if (typeof cmd.parameters.focus != "undefined") {
|
||||
focus = assert.boolean(cmd.parameters.focus,
|
||||
pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`);
|
||||
}
|
||||
|
||||
let type;
|
||||
if (typeof cmd.parameters.type != "undefined") {
|
||||
type = assert.string(cmd.parameters.type,
|
||||
pprint`Expected "type" to be a string, got ${cmd.parameters.type}`);
|
||||
}
|
||||
|
||||
// If an invalid or no type has been specified default to a tab.
|
||||
if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
|
||||
type = "tab";
|
||||
}
|
||||
|
||||
let contentBrowser;
|
||||
|
||||
switch (type) {
|
||||
case "window":
|
||||
let win = await this.curBrowser.openBrowserWindow(focus);
|
||||
contentBrowser = browser.getTabBrowser(win).selectedBrowser;
|
||||
break;
|
||||
|
||||
default:
|
||||
// To not fail if a new type gets added in the future, make opening
|
||||
// a new tab the default action.
|
||||
let tab = await this.curBrowser.openTab(focus);
|
||||
contentBrowser = browser.getBrowserForTab(tab);
|
||||
}
|
||||
|
||||
// Even with the framescript registered, the browser might not be known to
|
||||
// the parent process yet. Wait until it is available.
|
||||
// TODO: Fix by using `Browser:Init` or equivalent on bug 1311041
|
||||
let windowId = await new PollPromise((resolve, reject) => {
|
||||
let id = this.getIdForBrowser(contentBrowser);
|
||||
this.windowHandles.includes(id) ? resolve(id) : reject();
|
||||
});
|
||||
|
||||
return {handle: windowId.toString(), type};
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the currently selected tab/window.
|
||||
*
|
||||
@ -3086,16 +3148,14 @@ GeckoDriver.prototype.dismissDialog = async function() {
|
||||
let win = assert.open(this.getCurrentWindow());
|
||||
this._checkIfAlertIsPresent();
|
||||
|
||||
await new Promise(resolve => {
|
||||
win.addEventListener("DOMModalDialogClosed", async () => {
|
||||
await new IdlePromise(win);
|
||||
this.dialog = null;
|
||||
resolve();
|
||||
}, {once: true});
|
||||
let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
|
||||
|
||||
let {button0, button1} = this.dialog.ui;
|
||||
(button1 ? button1 : button0).click();
|
||||
});
|
||||
let {button0, button1} = this.dialog.ui;
|
||||
(button1 ? button1 : button0).click();
|
||||
|
||||
await dialogClosed;
|
||||
|
||||
this.dialog = null;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3106,16 +3166,14 @@ GeckoDriver.prototype.acceptDialog = async function() {
|
||||
let win = assert.open(this.getCurrentWindow());
|
||||
this._checkIfAlertIsPresent();
|
||||
|
||||
await new Promise(resolve => {
|
||||
win.addEventListener("DOMModalDialogClosed", async () => {
|
||||
await new IdlePromise(win);
|
||||
this.dialog = null;
|
||||
resolve();
|
||||
}, {once: true});
|
||||
let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
|
||||
|
||||
let {button0} = this.dialog.ui;
|
||||
button0.click();
|
||||
});
|
||||
let {button0} = this.dialog.ui;
|
||||
button0.click();
|
||||
|
||||
await dialogClosed;
|
||||
|
||||
this.dialog = null;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3286,15 +3344,10 @@ GeckoDriver.prototype.quit = async function(cmd) {
|
||||
this.deleteSession();
|
||||
|
||||
// delay response until the application is about to quit
|
||||
let quitApplication = new Promise(resolve => {
|
||||
Services.obs.addObserver(
|
||||
(subject, topic, data) => resolve(data),
|
||||
"quit-application");
|
||||
});
|
||||
|
||||
let quitApplication = waitForObserverTopic("quit-application");
|
||||
Services.startup.quit(mode);
|
||||
|
||||
return {cause: await quitApplication};
|
||||
return {cause: (await quitApplication).data};
|
||||
};
|
||||
|
||||
GeckoDriver.prototype.installAddon = function(cmd) {
|
||||
@ -3556,6 +3609,7 @@ GeckoDriver.prototype.commands = {
|
||||
"WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
|
||||
"WebDriver:Navigate": GeckoDriver.prototype.get,
|
||||
"WebDriver:NewSession": GeckoDriver.prototype.newSession,
|
||||
"WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
|
||||
"WebDriver:PerformActions": GeckoDriver.prototype.performActions,
|
||||
"WebDriver:Refresh": GeckoDriver.prototype.refresh,
|
||||
"WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
|
||||
@ -3591,5 +3645,5 @@ function restoreWindow(window) {
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}, {timeout: 2000});
|
||||
}
|
||||
|
@ -6,14 +6,12 @@ from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
from marionette_driver import By, Wait
|
||||
from marionette_driver import Wait
|
||||
from six import reraise
|
||||
|
||||
|
||||
class WindowManagerMixin(object):
|
||||
|
||||
_menu_item_new_tab = (By.ID, "menu_newNavigatorTab")
|
||||
|
||||
def setUp(self):
|
||||
super(WindowManagerMixin, self).setUp()
|
||||
|
||||
@ -60,15 +58,18 @@ class WindowManagerMixin(object):
|
||||
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
||||
def open_tab(self, trigger="menu"):
|
||||
def open_tab(self, callback=None, focus=False):
|
||||
current_tabs = self.marionette.window_handles
|
||||
|
||||
try:
|
||||
if callable(trigger):
|
||||
trigger()
|
||||
elif trigger == 'menu':
|
||||
with self.marionette.using_context("chrome"):
|
||||
self.marionette.find_element(*self._menu_item_new_tab).click()
|
||||
if callable(callback):
|
||||
callback()
|
||||
else:
|
||||
result = self.marionette.open(type="tab", focus=focus)
|
||||
if result["type"] != "tab":
|
||||
raise Exception(
|
||||
"Newly opened browsing context is of type {} and not tab.".format(
|
||||
result["type"]))
|
||||
except Exception:
|
||||
exc, val, tb = sys.exc_info()
|
||||
reraise(exc, 'Failed to trigger opening a new tab: {}'.format(val), tb)
|
||||
@ -82,8 +83,9 @@ class WindowManagerMixin(object):
|
||||
|
||||
return new_tab
|
||||
|
||||
def open_window(self, trigger=None):
|
||||
def open_window(self, callback=None, focus=False):
|
||||
current_windows = self.marionette.chrome_window_handles
|
||||
current_tabs = self.marionette.window_handles
|
||||
|
||||
def loaded(handle):
|
||||
with self.marionette.using_context("chrome"):
|
||||
@ -95,11 +97,14 @@ class WindowManagerMixin(object):
|
||||
""", script_args=[handle])
|
||||
|
||||
try:
|
||||
if callable(trigger):
|
||||
trigger()
|
||||
if callable(callback):
|
||||
callback()
|
||||
else:
|
||||
with self.marionette.using_context("chrome"):
|
||||
self.marionette.execute_script("OpenBrowserWindow();")
|
||||
result = self.marionette.open(type="window", focus=focus)
|
||||
if result["type"] != "window":
|
||||
raise Exception(
|
||||
"Newly opened browsing context is of type {} and not window.".format(
|
||||
result["type"]))
|
||||
except Exception:
|
||||
exc, val, tb = sys.exc_info()
|
||||
reraise(exc, 'Failed to trigger opening a new window: {}'.format(val), tb)
|
||||
@ -116,9 +121,16 @@ class WindowManagerMixin(object):
|
||||
lambda _: loaded(new_window),
|
||||
message="Window with handle '{}'' did not finish loading".format(new_window))
|
||||
|
||||
return new_window
|
||||
# Bug 1507771 - Return the correct handle based on the currently selected context
|
||||
# as long as "WebDriver:NewWindow" is not handled separtely in chrome context
|
||||
context = self.marionette._send_message("Marionette:GetContext", key="value")
|
||||
if context == "chrome":
|
||||
return new_window
|
||||
elif context == "content":
|
||||
[new_tab] = list(set(self.marionette.window_handles) - set(current_tabs))
|
||||
return new_tab
|
||||
|
||||
def open_chrome_window(self, url):
|
||||
def open_chrome_window(self, url, focus=False):
|
||||
"""Open a new chrome window with the specified chrome URL.
|
||||
|
||||
Can be replaced with "WebDriver:NewWindow" once the command
|
||||
@ -166,4 +178,5 @@ class WindowManagerMixin(object):
|
||||
})();
|
||||
""", script_args=(url,))
|
||||
|
||||
return self.open_window(trigger=open_with_js)
|
||||
with self.marionette.using_context("chrome"):
|
||||
return self.open_window(callback=open_with_js, focus=focus)
|
||||
|
@ -1,22 +1,5 @@
|
||||
#Copyright 2007-2009 WebDriver committers
|
||||
#Copyright 2007-2009 Google Inc.
|
||||
#
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from marionette_driver import By
|
||||
|
||||
from marionette_harness import MarionetteTestCase, WindowManagerMixin
|
||||
|
||||
|
||||
@ -25,18 +8,13 @@ class ChromeTests(WindowManagerMixin, MarionetteTestCase):
|
||||
def setUp(self):
|
||||
super(ChromeTests, self).setUp()
|
||||
|
||||
self.marionette.set_context('chrome')
|
||||
|
||||
def tearDown(self):
|
||||
self.close_all_windows()
|
||||
super(ChromeTests, self).tearDown()
|
||||
|
||||
def test_hang_until_timeout(self):
|
||||
def open_with_menu():
|
||||
menu = self.marionette.find_element(By.ID, 'aboutName')
|
||||
menu.click()
|
||||
|
||||
new_window = self.open_window(trigger=open_with_menu)
|
||||
with self.marionette.using_context("chrome"):
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
try:
|
||||
@ -45,7 +23,8 @@ class ChromeTests(WindowManagerMixin, MarionetteTestCase):
|
||||
# while running this test. Otherwise it would mask eg. IOError as
|
||||
# thrown for a socket timeout.
|
||||
raise NotImplementedError('Exception should not cause a hang when '
|
||||
'closing the chrome window')
|
||||
'closing the chrome window in content '
|
||||
'context')
|
||||
finally:
|
||||
self.marionette.close_chrome_window()
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
@ -449,20 +449,16 @@ class TestClickCloseContext(WindowManagerMixin, MarionetteTestCase):
|
||||
super(TestClickCloseContext, self).tearDown()
|
||||
|
||||
def test_click_close_tab(self):
|
||||
self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
|
||||
tab = self.open_tab(
|
||||
lambda: self.marionette.find_element(By.ID, "new-tab").click())
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
self.marionette.navigate(self.test_page)
|
||||
self.marionette.find_element(By.ID, "close-window").click()
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_click_close_window(self):
|
||||
self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
|
||||
win = self.open_window(
|
||||
lambda: self.marionette.find_element(By.ID, "new-window").click())
|
||||
self.marionette.switch_to_window(win)
|
||||
new_tab = self.open_window()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
self.marionette.navigate(self.test_page)
|
||||
self.marionette.find_element(By.ID, "close-window").click()
|
||||
|
@ -77,22 +77,3 @@ class TestKeyActions(WindowManagerMixin, MarionetteTestCase):
|
||||
.key_down("x")
|
||||
.perform())
|
||||
self.assertEqual(self.key_reporter_value, "")
|
||||
|
||||
@skip_if_mobile("Interacting with chrome windows not available for Fennec")
|
||||
def test_open_in_new_window_shortcut(self):
|
||||
|
||||
def open_window_with_action():
|
||||
el = self.marionette.find_element(By.TAG_NAME, "a")
|
||||
(self.key_action.key_down(Keys.SHIFT)
|
||||
.press(el)
|
||||
.release()
|
||||
.key_up(Keys.SHIFT)
|
||||
.perform())
|
||||
|
||||
self.marionette.navigate(inline("<a href='#'>Click</a>"))
|
||||
new_window = self.open_window(trigger=open_window_with_action)
|
||||
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.marionette.close_chrome_window()
|
||||
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
@ -54,13 +54,8 @@ class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
|
||||
else:
|
||||
self.mod_key = Keys.CONTROL
|
||||
|
||||
def open_with_link():
|
||||
link = self.marionette.find_element(By.ID, "new-blank-tab")
|
||||
link.click()
|
||||
|
||||
# Always use a blank new tab for an empty history
|
||||
self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
|
||||
self.new_tab = self.open_tab(open_with_link)
|
||||
self.new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(self.new_tab)
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda _: self.history_length == 1,
|
||||
@ -297,7 +292,6 @@ class TestNavigate(BaseNavigationTestCase):
|
||||
focus_el = self.marionette.find_element(By.CSS_SELECTOR, ":focus")
|
||||
self.assertEqual(self.marionette.get_active_element(), focus_el)
|
||||
|
||||
@skip_if_mobile("Needs application independent method to open a new tab")
|
||||
def test_no_hang_when_navigating_after_closing_original_tab(self):
|
||||
# Close the start tab
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
@ -338,22 +332,6 @@ class TestNavigate(BaseNavigationTestCase):
|
||||
message="'{}' hasn't been loaded".format(self.test_page_remote))
|
||||
self.assertTrue(self.is_remote_tab)
|
||||
|
||||
@skip_if_mobile("On Android no shortcuts are available")
|
||||
def test_navigate_shortcut_key(self):
|
||||
|
||||
def open_with_shortcut():
|
||||
self.marionette.navigate(self.test_page_remote)
|
||||
with self.marionette.using_context("chrome"):
|
||||
main_win = self.marionette.find_element(By.ID, "main-window")
|
||||
main_win.send_keys(self.mod_key, Keys.SHIFT, "a")
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_shortcut)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == "about:addons",
|
||||
message="'about:addons' hasn't been loaded")
|
||||
|
||||
|
||||
class TestBackForwardNavigation(BaseNavigationTestCase):
|
||||
|
||||
|
@ -253,7 +253,8 @@ class TestScreenCaptureChrome(WindowManagerMixin, ScreenCaptureTestCase):
|
||||
|
||||
chrome_document_element = self.document_element
|
||||
with self.marionette.using_context('content'):
|
||||
self.assertRaisesRegexp(NoSuchElementException, "Web element reference not seen before",
|
||||
self.assertRaisesRegexp(NoSuchElementException,
|
||||
"Web element reference not seen before",
|
||||
self.marionette.screenshot,
|
||||
highlights=[chrome_document_element])
|
||||
|
||||
@ -274,10 +275,9 @@ class TestScreenCaptureContent(WindowManagerMixin, ScreenCaptureTestCase):
|
||||
return [document.body.scrollWidth, document.body.scrollHeight]
|
||||
"""))
|
||||
|
||||
@skip_if_mobile("Needs application independent method to open a new tab")
|
||||
def test_capture_tab_already_closed(self):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
|
||||
self.assertRaises(NoSuchWindowException, self.marionette.screenshot)
|
||||
|
@ -6,9 +6,8 @@ from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest import skipIf
|
||||
|
||||
from marionette_driver import By
|
||||
from unittest import skipIf
|
||||
|
||||
# add this directory to the path
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
@ -28,119 +27,70 @@ class TestSwitchWindowChrome(TestSwitchToWindowContent):
|
||||
|
||||
super(TestSwitchWindowChrome, self).tearDown()
|
||||
|
||||
def open_window_in_background(self):
|
||||
with self.marionette.using_context("chrome"):
|
||||
self.marionette.execute_async_script("""
|
||||
let callback = arguments[0];
|
||||
(async function() {
|
||||
function promiseEvent(target, type, args) {
|
||||
return new Promise(r => {
|
||||
let params = Object.assign({once: true}, args);
|
||||
target.addEventListener(type, r, params);
|
||||
});
|
||||
}
|
||||
function promiseWindowFocus(w) {
|
||||
return Promise.all([
|
||||
promiseEvent(w, "focus", {capture: true}),
|
||||
promiseEvent(w, "activate"),
|
||||
]);
|
||||
}
|
||||
// Open a window, wait for it to receive focus
|
||||
let win = OpenBrowserWindow();
|
||||
await promiseWindowFocus(win);
|
||||
|
||||
// Now refocus our original window and wait for that to happen.
|
||||
let windowFocusPromise = promiseWindowFocus(window);
|
||||
window.focus();
|
||||
return windowFocusPromise;
|
||||
})().then(() => {
|
||||
// can't just pass `callback`, as we can't JSON-ify the events it'd get passed.
|
||||
callback()
|
||||
});
|
||||
""")
|
||||
|
||||
def open_window_in_foreground(self):
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(self.test_page)
|
||||
link = self.marionette.find_element(By.ID, "new-window")
|
||||
link.click()
|
||||
|
||||
@skipIf(sys.platform.startswith("linux"),
|
||||
"Bug 1511970 - New window isn't moved to the background on Linux")
|
||||
def test_switch_tabs_for_new_background_window_without_focus_change(self):
|
||||
# Open an addition tab in the original window so we can better check
|
||||
# Open an additional tab in the original window so we can better check
|
||||
# the selected index in thew new window to be opened.
|
||||
second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
|
||||
second_tab = self.open_tab(focus=True)
|
||||
self.marionette.switch_to_window(second_tab, focus=True)
|
||||
second_tab_index = self.get_selected_tab_index()
|
||||
self.assertNotEqual(second_tab_index, self.selected_tab_index)
|
||||
|
||||
# Opens a new background window, but we are interested in the tab
|
||||
tab_in_new_window = self.open_tab(trigger=self.open_window_in_background)
|
||||
# Open a new background window, but we are interested in the tab
|
||||
with self.marionette.using_context("content"):
|
||||
tab_in_new_window = self.open_window()
|
||||
self.assertEqual(self.marionette.current_window_handle, second_tab)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.empty_page)
|
||||
|
||||
# Switch to the tab in the new window but don't focus it
|
||||
self.marionette.switch_to_window(tab_in_new_window, focus=False)
|
||||
self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
|
||||
self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), "about:blank")
|
||||
|
||||
def test_switch_tabs_for_new_foreground_window_with_focus_change(self):
|
||||
# Open an addition tab in the original window so we can better check
|
||||
# the selected index in thew new window to be opened.
|
||||
second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
|
||||
second_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(second_tab, focus=True)
|
||||
second_tab_index = self.get_selected_tab_index()
|
||||
self.assertNotEqual(second_tab_index, self.selected_tab_index)
|
||||
|
||||
# Opens a new window, but we are interested in the tab
|
||||
tab_in_new_window = self.open_tab(trigger=self.open_window_in_foreground)
|
||||
with self.marionette.using_context("content"):
|
||||
tab_in_new_window = self.open_window(focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, second_tab)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.marionette.switch_to_window(tab_in_new_window)
|
||||
self.assertEqual(self.marionette.current_window_handle, tab_in_new_window)
|
||||
self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.empty_page)
|
||||
|
||||
self.marionette.switch_to_window(second_tab, focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, second_tab)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
# Bug 1335085 - The focus doesn't change even as requested so.
|
||||
# self.assertEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_switch_tabs_for_new_foreground_window_without_focus_change(self):
|
||||
# Open an addition tab in the original window so we can better check
|
||||
# the selected index in thew new window to be opened.
|
||||
second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
|
||||
second_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(second_tab, focus=True)
|
||||
second_tab_index = self.get_selected_tab_index()
|
||||
self.assertNotEqual(second_tab_index, self.selected_tab_index)
|
||||
|
||||
# Opens a new window, but we are interested in the tab which automatically
|
||||
# gets the focus.
|
||||
self.open_tab(trigger=self.open_window_in_foreground)
|
||||
self.open_window(focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, second_tab)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
# Switch to the second tab in the first window, but don't focus it.
|
||||
self.marionette.switch_to_window(second_tab, focus=False)
|
||||
self.assertEqual(self.marionette.current_window_handle, second_tab)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), second_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
@ -4,10 +4,10 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from marionette_driver import Actions, By, Wait
|
||||
from marionette_driver import By
|
||||
from marionette_driver.keys import Keys
|
||||
|
||||
from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
|
||||
from marionette_harness import MarionetteTestCase, WindowManagerMixin
|
||||
|
||||
|
||||
class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
@ -20,14 +20,8 @@ class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
else:
|
||||
self.mod_key = Keys.CONTROL
|
||||
|
||||
self.empty_page = self.marionette.absolute_url("empty.html")
|
||||
self.test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
|
||||
self.selected_tab_index = self.get_selected_tab_index()
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(self.test_page)
|
||||
|
||||
def tearDown(self):
|
||||
self.close_all_tabs()
|
||||
|
||||
@ -69,78 +63,51 @@ class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
}
|
||||
""")
|
||||
|
||||
def open_tab_in_background(self):
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
|
||||
action = Actions(self.marionette)
|
||||
action.key_down(self.mod_key).click(link).perform()
|
||||
|
||||
def open_tab_in_foreground(self):
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
def test_switch_tabs_with_focus_change(self):
|
||||
new_tab = self.open_tab(self.open_tab_in_foreground)
|
||||
new_tab = self.open_tab(focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
# Switch to new tab first because it is already selected
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda _: self.marionette.get_url() == self.empty_page,
|
||||
message="{} has been loaded in the newly opened tab.".format(self.empty_page))
|
||||
|
||||
# Switch to original tab by explicitely setting the focus
|
||||
self.marionette.switch_to_window(self.start_tab, focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_switch_tabs_without_focus_change(self):
|
||||
new_tab = self.open_tab(self.open_tab_in_foreground)
|
||||
new_tab = self.open_tab(focus=True)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
# Switch to new tab first because it is already selected
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
|
||||
# Switch to original tab by explicitely not setting the focus
|
||||
self.marionette.switch_to_window(self.start_tab, focus=False)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertNotEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertEqual(self.get_selected_tab_index(), self.selected_tab_index)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_switch_from_content_to_chrome_window_should_not_change_selected_tab(self):
|
||||
new_tab = self.open_tab(self.open_tab_in_foreground)
|
||||
new_tab = self.open_tab(focus=True)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
@ -150,24 +117,31 @@ class TestSwitchToWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
self.assertEqual(self.get_selected_tab_index(), new_tab_index)
|
||||
|
||||
@skip_if_mobile("New windows not supported in Fennec")
|
||||
def test_switch_to_new_private_browsing_window_has_to_register_browsers(self):
|
||||
def test_switch_to_new_private_browsing_tab(self):
|
||||
# Test that tabs (browsers) are correctly registered for a newly opened
|
||||
# private browsing window. This has to also happen without explicitely
|
||||
# private browsing window/tab. This has to also happen without explicitely
|
||||
# switching to the tab itself before using any commands in content scope.
|
||||
#
|
||||
# Note: Not sure why this only affects private browsing windows only.
|
||||
new_tab = self.open_tab(focus=True)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
def open_private_browsing_window():
|
||||
def open_private_browsing_window_firefox():
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate("about:privatebrowsing")
|
||||
button = self.marionette.find_element(By.ID, "startPrivateBrowsing")
|
||||
button.click()
|
||||
self.marionette.find_element(By.ID, "startPrivateBrowsing").click()
|
||||
|
||||
new_window = self.open_window(open_private_browsing_window)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
|
||||
self.assertNotEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
def open_private_browsing_tab_fennec():
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.find_element(By.ID, "newPrivateTabLink").click()
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.execute_script(" return true; ")
|
||||
self.marionette.navigate("about:privatebrowsing")
|
||||
if self.marionette.session_capabilities["browserName"] == "fennec":
|
||||
new_pb_tab = self.open_tab(open_private_browsing_tab_fennec)
|
||||
else:
|
||||
new_pb_tab = self.open_tab(open_private_browsing_window_firefox)
|
||||
|
||||
self.marionette.switch_to_window(new_pb_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, new_pb_tab)
|
||||
|
||||
self.marionette.execute_script(" return true; ")
|
||||
|
@ -21,14 +21,14 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
super(TestCloseWindow, self).tearDown()
|
||||
|
||||
def test_close_chrome_window_for_browser_window(self):
|
||||
win = self.open_window()
|
||||
self.marionette.switch_to_window(win)
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
chrome_window_handles = self.marionette.close_chrome_window()
|
||||
self.assertNotIn(win, chrome_window_handles)
|
||||
self.assertNotIn(new_window, chrome_window_handles)
|
||||
self.assertListEqual(self.start_windows, chrome_window_handles)
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
|
||||
def test_close_chrome_window_for_non_browser_window(self):
|
||||
win = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
@ -50,20 +50,20 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertIsNotNone(self.marionette.session)
|
||||
|
||||
def test_close_window_for_browser_tab(self):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
window_handles = self.marionette.close()
|
||||
self.assertNotIn(tab, window_handles)
|
||||
self.assertNotIn(new_tab, window_handles)
|
||||
self.assertListEqual(self.start_tabs, window_handles)
|
||||
|
||||
def test_close_window_for_browser_window_with_single_tab(self):
|
||||
win = self.open_window()
|
||||
self.marionette.switch_to_window(win)
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
|
||||
window_handles = self.marionette.close()
|
||||
self.assertNotIn(win, window_handles)
|
||||
self.assertNotIn(new_window, window_handles)
|
||||
self.assertListEqual(self.start_tabs, window_handles)
|
||||
self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
|
||||
|
||||
|
@ -24,26 +24,27 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
|
||||
@skip_if_mobile("Interacting with chrome windows not available for Fennec")
|
||||
def test_close_chrome_window_for_browser_window(self):
|
||||
win = self.open_window()
|
||||
self.marionette.switch_to_window(win)
|
||||
with self.marionette.using_context("chrome"):
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertIn(new_window, self.marionette.chrome_window_handles)
|
||||
chrome_window_handles = self.marionette.close_chrome_window()
|
||||
self.assertNotIn(win, chrome_window_handles)
|
||||
self.assertNotIn(new_window, chrome_window_handles)
|
||||
self.assertListEqual(self.start_windows, chrome_window_handles)
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
|
||||
@skip_if_mobile("Interacting with chrome windows not available for Fennec")
|
||||
def test_close_chrome_window_for_non_browser_window(self):
|
||||
win = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(win)
|
||||
new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
self.assertIn(win, self.marionette.chrome_window_handles)
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertIn(new_window, self.marionette.chrome_window_handles)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
chrome_window_handles = self.marionette.close_chrome_window()
|
||||
self.assertNotIn(win, chrome_window_handles)
|
||||
self.assertNotIn(new_window, chrome_window_handles)
|
||||
self.assertListEqual(self.start_windows, chrome_window_handles)
|
||||
self.assertNotIn(win, self.marionette.window_handles)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
|
||||
@skip_if_mobile("Interacting with chrome windows not available for Fennec")
|
||||
def test_close_chrome_window_for_last_open_window(self):
|
||||
@ -54,19 +55,17 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertListEqual([self.start_window], self.marionette.chrome_window_handles)
|
||||
self.assertIsNotNone(self.marionette.session)
|
||||
|
||||
@skip_if_mobile("Needs application independent method to open a new tab")
|
||||
def test_close_window_for_browser_tab(self):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
window_handles = self.marionette.close()
|
||||
self.assertNotIn(tab, window_handles)
|
||||
self.assertNotIn(new_tab, window_handles)
|
||||
self.assertListEqual(self.start_tabs, window_handles)
|
||||
|
||||
@skip_if_mobile("Needs application independent method to open a new tab")
|
||||
def test_close_window_with_dismissed_beforeunload_prompt(self):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
self.marionette.navigate(inline("""
|
||||
<input type="text">
|
||||
@ -82,12 +81,12 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
|
||||
@skip_if_mobile("Interacting with chrome windows not available for Fennec")
|
||||
def test_close_window_for_browser_window_with_single_tab(self):
|
||||
win = self.open_window()
|
||||
self.marionette.switch_to_window(win)
|
||||
new_tab = self.open_window()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
self.assertEqual(len(self.start_tabs) + 1, len(self.marionette.window_handles))
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
window_handles = self.marionette.close()
|
||||
self.assertNotIn(win, window_handles)
|
||||
self.assertNotIn(new_tab, window_handles)
|
||||
self.assertListEqual(self.start_tabs, window_handles)
|
||||
self.assertListEqual(self.start_windows, self.marionette.chrome_window_handles)
|
||||
|
||||
@ -104,8 +103,8 @@ class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
self.close_all_tabs()
|
||||
|
||||
test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.navigate(test_page)
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
|
@ -6,7 +6,7 @@ from __future__ import absolute_import
|
||||
|
||||
import types
|
||||
|
||||
from marionette_driver import By, errors, Wait
|
||||
from marionette_driver import errors
|
||||
|
||||
from marionette_harness import MarionetteTestCase, WindowManagerMixin
|
||||
|
||||
@ -16,9 +16,7 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
def setUp(self):
|
||||
super(TestWindowHandles, self).setUp()
|
||||
|
||||
self.empty_page = self.marionette.absolute_url("empty.html")
|
||||
self.test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
self.marionette.navigate(self.test_page)
|
||||
self.xul_dialog = "chrome://marionette/content/test_dialog.xul"
|
||||
|
||||
self.marionette.set_context("chrome")
|
||||
|
||||
@ -42,17 +40,16 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertIsInstance(handle, types.StringTypes)
|
||||
|
||||
def test_chrome_window_handles_with_scopes(self):
|
||||
# Open a browser and a non-browser (about window) chrome window
|
||||
self.open_window(
|
||||
trigger=lambda: self.marionette.execute_script("OpenBrowserWindow();"))
|
||||
new_browser = self.open_window()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
|
||||
self.assertIn(new_browser, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
self.open_window(
|
||||
trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
|
||||
new_dialog = self.open_chrome_window(self.xul_dialog)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2)
|
||||
self.assertIn(new_dialog, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
chrome_window_handles_in_chrome_scope = self.marionette.chrome_window_handles
|
||||
@ -64,117 +61,112 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertEqual(self.marionette.window_handles,
|
||||
window_handles_in_chrome_scope)
|
||||
|
||||
def test_chrome_window_handles_after_opening_new_dialog(self):
|
||||
xul_dialog = "chrome://marionette/content/test_dialog.xul"
|
||||
new_win = self.open_chrome_window(xul_dialog)
|
||||
def test_chrome_window_handles_after_opening_new_chrome_window(self):
|
||||
new_window = self.open_chrome_window(self.xul_dialog)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
|
||||
self.assertIn(new_window, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
# Check that the new tab has the correct page loaded
|
||||
self.marionette.switch_to_window(new_win)
|
||||
# Check that the new chrome window has the correct URL loaded
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
|
||||
self.assertEqual(self.marionette.get_url(), xul_dialog)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
|
||||
self.assertEqual(self.marionette.get_url(), self.xul_dialog)
|
||||
|
||||
# Close the opened dialog and carry on in our original tab.
|
||||
# Close the chrome window, and carry on in our original window.
|
||||
self.marionette.close_chrome_window()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
|
||||
self.assertNotIn(new_window, self.marionette.chrome_window_handles)
|
||||
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_chrome_window_handles_after_opening_new_window(self):
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-window")
|
||||
link.click()
|
||||
|
||||
# We open a new window but are actually interested in the new tab
|
||||
new_win = self.open_window(trigger=open_with_link)
|
||||
new_window = self.open_window()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
|
||||
self.assertIn(new_window, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
# Check that the new tab has the correct page loaded
|
||||
self.marionette.switch_to_window(new_win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_win)
|
||||
with self.marionette.using_context("content"):
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == self.empty_page,
|
||||
message="{} did not load after opening a new tab".format(self.empty_page))
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
|
||||
|
||||
# Ensure navigate works in our current window
|
||||
other_page = self.marionette.absolute_url("test.html")
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(other_page)
|
||||
self.assertEqual(self.marionette.get_url(), other_page)
|
||||
|
||||
# Close the opened window and carry on in our original tab.
|
||||
# Close the opened window and carry on in our original window.
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
|
||||
self.assertNotIn(new_window, self.marionette.chrome_window_handles)
|
||||
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_window_handles_after_opening_new_tab(self):
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
with self.marionette.using_context("content"):
|
||||
new_tab = self.open_tab()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertIn(new_tab, self.marionette.window_handles)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == self.empty_page,
|
||||
message="{} did not load after opening a new tab".format(self.empty_page))
|
||||
|
||||
# Ensure navigate works in our current tab
|
||||
other_page = self.marionette.absolute_url("test.html")
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(other_page)
|
||||
self.assertEqual(self.marionette.get_url(), other_page)
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertNotIn(new_tab, self.marionette.window_handles)
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
def test_window_handles_after_opening_new_dialog(self):
|
||||
xul_dialog = "chrome://marionette/content/test_dialog.xul"
|
||||
new_win = self.open_chrome_window(xul_dialog)
|
||||
def test_window_handles_after_opening_new_foreground_tab(self):
|
||||
with self.marionette.using_context("content"):
|
||||
new_tab = self.open_tab(focus=True)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertIn(new_tab, self.marionette.window_handles)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
# We still have the default tab set as our window handle. This
|
||||
# get_url command should be sent immediately, and not be forever-queued.
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.get_url()
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertNotIn(new_tab, self.marionette.window_handles)
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
self.marionette.switch_to_window(new_win)
|
||||
def test_window_handles_after_opening_new_chrome_window(self):
|
||||
new_window = self.open_chrome_window(self.xul_dialog)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.get_url(), xul_dialog)
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.get_url(), self.xul_dialog)
|
||||
|
||||
# Check that the opened dialog is not accessible via window handles
|
||||
with self.assertRaises(errors.NoSuchWindowException):
|
||||
@ -190,111 +182,23 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_window_handles_after_opening_new_window(self):
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-window")
|
||||
link.click()
|
||||
|
||||
# We open a new window but are actually interested in the new tab
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
# Check that the new tab has the correct page loaded
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == self.empty_page,
|
||||
message="{} did not load after opening a new tab".format(self.empty_page))
|
||||
|
||||
# Ensure navigate works in our current window
|
||||
other_page = self.marionette.absolute_url("test.html")
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(other_page)
|
||||
self.assertEqual(self.marionette.get_url(), other_page)
|
||||
|
||||
# Close the opened window and carry on in our original tab.
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
def test_window_handles_after_closing_original_tab(self):
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
with self.marionette.using_context("content"):
|
||||
new_tab = self.open_tab()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertIn(new_tab, self.marionette.window_handles)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertIn(new_tab, self.marionette.window_handles)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
with self.marionette.using_context("content"):
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == self.empty_page,
|
||||
message="{} did not load after opening a new tab".format(self.empty_page))
|
||||
|
||||
def test_window_handles_no_switch(self):
|
||||
"""Regression test for bug 1294456.
|
||||
This test is testing the case where Marionette attempts to send a
|
||||
command to a window handle when the browser has opened and selected
|
||||
a new tab. Before bug 1294456 landed, the Marionette driver was getting
|
||||
confused about which window handle the client cared about, and assumed
|
||||
it was the window handle for the newly opened and selected tab.
|
||||
|
||||
This caused Marionette to think that the browser needed to do a remoteness
|
||||
flip in the e10s case, since the tab opened by menu_newNavigatorTab is
|
||||
about:newtab (which is currently non-remote). This meant that commands
|
||||
sent to what should have been the original window handle would be
|
||||
queued and never sent, since the remoteness flip in the new tab was
|
||||
never going to happen.
|
||||
"""
|
||||
def open_with_menu():
|
||||
menu_new_tab = self.marionette.find_element(By.ID, 'menu_newNavigatorTab')
|
||||
menu_new_tab.click()
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_menu)
|
||||
self.assert_window_handles()
|
||||
|
||||
# We still have the default tab set as our window handle. This
|
||||
# get_url command should be sent immediately, and not be forever-queued.
|
||||
with self.marionette.using_context("content"):
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
def test_window_handles_after_closing_last_window(self):
|
||||
self.close_all_windows()
|
||||
|
@ -7,7 +7,7 @@ from __future__ import absolute_import
|
||||
import types
|
||||
import urllib
|
||||
|
||||
from marionette_driver import By, errors, Wait
|
||||
from marionette_driver import errors
|
||||
|
||||
from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
|
||||
|
||||
@ -21,9 +21,7 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
def setUp(self):
|
||||
super(TestWindowHandles, self).setUp()
|
||||
|
||||
self.empty_page = self.marionette.absolute_url("empty.html")
|
||||
self.test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
self.marionette.navigate(self.test_page)
|
||||
self.xul_dialog = "chrome://marionette/content/test_dialog.xul"
|
||||
|
||||
def tearDown(self):
|
||||
self.close_all_tabs()
|
||||
@ -39,12 +37,8 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
for handle in self.marionette.window_handles:
|
||||
self.assertIsInstance(handle, types.StringTypes)
|
||||
|
||||
def test_window_handles_after_opening_new_tab(self):
|
||||
def open_with_link():
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
def tst_window_handles_after_opening_new_tab(self):
|
||||
new_tab = self.open_tab()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
@ -52,13 +46,9 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: mn.get_url() == self.empty_page,
|
||||
message="{} did not load after opening a new tab".format(self.empty_page))
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
@ -69,29 +59,15 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
def test_window_handles_after_opening_new_browser_window(self):
|
||||
def open_with_link():
|
||||
link = self.marionette.find_element(By.ID, "new-window")
|
||||
link.click()
|
||||
|
||||
# We open a new window but are actually interested in the new tab
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
def tst_window_handles_after_opening_new_browser_window(self):
|
||||
new_tab = self.open_window()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
# Check that the new tab has the correct page loaded
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
Wait(self.marionette, self.marionette.timeout.page_load).until(
|
||||
lambda _: self.marionette.get_url() == self.empty_page,
|
||||
message="The expected page '{}' has not been loaded".format(self.empty_page))
|
||||
|
||||
# Ensure navigate works in our current window
|
||||
other_page = self.marionette.absolute_url("test.html")
|
||||
self.marionette.navigate(other_page)
|
||||
self.assertEqual(self.marionette.get_url(), other_page)
|
||||
|
||||
# Close the opened window and carry on in our original tab.
|
||||
self.marionette.close()
|
||||
@ -101,31 +77,16 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertEqual(self.marionette.get_url(), self.test_page)
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_window_handles_after_opening_new_non_browser_window(self):
|
||||
def open_with_link():
|
||||
self.marionette.navigate(inline("""
|
||||
<a id="blob-download" download="foo.html">Download</a>
|
||||
|
||||
<script>
|
||||
const string = "test";
|
||||
const blob = new Blob([string], { type: "text/html" });
|
||||
|
||||
const link = document.getElementById("blob-download");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
</script>
|
||||
"""))
|
||||
link = self.marionette.find_element(By.ID, "blob-download")
|
||||
link.click()
|
||||
|
||||
new_win = self.open_window(trigger=open_with_link)
|
||||
def tst_window_handles_after_opening_new_non_browser_window(self):
|
||||
new_window = self.open_chrome_window(self.xul_dialog)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertNotIn(new_window, self.marionette.window_handles)
|
||||
|
||||
self.marionette.switch_to_window(new_win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assert_window_handles()
|
||||
|
||||
# Check that the opened window is not accessible via window handles
|
||||
@ -144,26 +105,21 @@ class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
|
||||
def test_window_handles_after_closing_original_tab(self):
|
||||
def open_with_link():
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
new_tab = self.open_tab(trigger=open_with_link)
|
||||
new_tab = self.open_tab()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
|
||||
self.assertEqual(self.marionette.current_window_handle, self.start_tab)
|
||||
self.assertIn(new_tab, self.marionette.window_handles)
|
||||
|
||||
self.marionette.close()
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs))
|
||||
self.assertNotIn(self.start_tab, self.marionette.window_handles)
|
||||
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.assert_window_handles()
|
||||
self.assertEqual(self.marionette.current_window_handle, new_tab)
|
||||
Wait(self.marionette, self.marionette.timeout.page_load).until(
|
||||
lambda _: self.marionette.get_url() == self.empty_page,
|
||||
message="The expected page '{}' has not been loaded".format(self.empty_page))
|
||||
|
||||
def test_window_handles_after_closing_last_tab(self):
|
||||
def tst_window_handles_after_closing_last_tab(self):
|
||||
self.close_all_tabs()
|
||||
self.assertEqual(self.marionette.close(), [])
|
||||
|
@ -21,15 +21,9 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_closed_chrome_window(self):
|
||||
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
self.marionette.navigate(test_page)
|
||||
self.marionette.find_element(By.ID, "new-window").click()
|
||||
|
||||
win = self.open_window(open_with_link)
|
||||
self.marionette.switch_to_window(win)
|
||||
with self.marionette.using_context("chrome"):
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.marionette.close_chrome_window()
|
||||
|
||||
# When closing a browser window both handles are not available
|
||||
@ -43,12 +37,12 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_closed_chrome_window_while_in_frame(self):
|
||||
win = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(win)
|
||||
new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(new_window)
|
||||
with self.marionette.using_context("chrome"):
|
||||
self.marionette.switch_to_frame("iframe")
|
||||
self.marionette.close_chrome_window()
|
||||
@ -61,13 +55,12 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
def test_closed_tab(self):
|
||||
with self.marionette.using_context("content"):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.close()
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
|
||||
# Check that only the content window is not available in both contexts
|
||||
for context in ("chrome", "content"):
|
||||
@ -79,25 +72,26 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
def test_closed_tab_while_in_frame(self):
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
tab = self.open_tab()
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
|
||||
frame = self.marionette.find_element(By.ID, "test_iframe")
|
||||
self.marionette.switch_to_frame(frame)
|
||||
self.marionette.close()
|
||||
self.marionette.close()
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.current_window_handle
|
||||
self.marionette.current_chrome_window_handle
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.current_window_handle
|
||||
self.marionette.current_chrome_window_handle
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
|
||||
class TestNoSuchWindowChrome(TestNoSuchWindowContent):
|
||||
@ -121,42 +115,22 @@ class TestSwitchWindow(WindowManagerMixin, MarionetteTestCase):
|
||||
self.close_all_windows()
|
||||
super(TestSwitchWindow, self).tearDown()
|
||||
|
||||
def test_windows(self):
|
||||
def open_browser_with_js():
|
||||
self.marionette.execute_script(" window.open(); ")
|
||||
|
||||
new_window = self.open_window(trigger=open_browser_with_js)
|
||||
def test_switch_window_after_open_and_close(self):
|
||||
with self.marionette.using_context("chrome"):
|
||||
new_window = self.open_window()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
|
||||
self.assertIn(new_window, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
# switch to the other window
|
||||
# switch to the new chrome window and close it
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
|
||||
self.assertNotEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
|
||||
# switch back and close original window
|
||||
self.marionette.close_chrome_window()
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
|
||||
self.assertNotIn(new_window, self.marionette.chrome_window_handles)
|
||||
|
||||
# switch back to the original chrome window
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
|
||||
self.marionette.close_chrome_window()
|
||||
|
||||
self.assertNotIn(self.start_window, self.marionette.chrome_window_handles)
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows))
|
||||
|
||||
def test_should_load_and_close_a_window(self):
|
||||
def open_window_with_link():
|
||||
test_html = self.marionette.absolute_url("test_windows.html")
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(test_html)
|
||||
self.marionette.find_element(By.LINK_TEXT, "Open new window").click()
|
||||
|
||||
new_window = self.open_window(trigger=open_window_with_link)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.assertEqual(self.marionette.current_chrome_window_handle, new_window)
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), 2)
|
||||
|
||||
with self.marionette.using_context('content'):
|
||||
self.assertEqual(self.marionette.title, "We Arrive Here")
|
||||
|
||||
# Let's close and check
|
||||
self.marionette.close_chrome_window()
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
self.assertEqual(len(self.marionette.chrome_window_handles), 1)
|
||||
|
@ -15,30 +15,15 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
def setUp(self):
|
||||
super(TestNoSuchWindowContent, self).setUp()
|
||||
|
||||
self.test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
with self.marionette.using_context("content"):
|
||||
self.marionette.navigate(self.test_page)
|
||||
|
||||
def tearDown(self):
|
||||
self.close_all_windows()
|
||||
super(TestNoSuchWindowContent, self).tearDown()
|
||||
|
||||
def open_tab_in_foreground(self):
|
||||
with self.marionette.using_context("content"):
|
||||
link = self.marionette.find_element(By.ID, "new-tab")
|
||||
link.click()
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_closed_chrome_window(self):
|
||||
|
||||
def open_with_link():
|
||||
with self.marionette.using_context("content"):
|
||||
test_page = self.marionette.absolute_url("windowHandles.html")
|
||||
self.marionette.navigate(test_page)
|
||||
self.marionette.find_element(By.ID, "new-window").click()
|
||||
|
||||
win = self.open_window(open_with_link)
|
||||
self.marionette.switch_to_window(win)
|
||||
with self.marionette.using_context("chrome"):
|
||||
new_window = self.open_window()
|
||||
self.marionette.switch_to_window(new_window)
|
||||
self.marionette.close_chrome_window()
|
||||
|
||||
# When closing a browser window both handles are not available
|
||||
@ -52,12 +37,13 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
@skip_if_mobile("Fennec doesn't support other chrome windows")
|
||||
def test_closed_chrome_window_while_in_frame(self):
|
||||
win = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(win)
|
||||
new_window = self.open_chrome_window("chrome://marionette/content/test.xul")
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
with self.marionette.using_context("chrome"):
|
||||
self.marionette.switch_to_frame("iframe")
|
||||
self.marionette.close_chrome_window()
|
||||
@ -70,13 +56,12 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_window)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(win)
|
||||
self.marionette.switch_to_window(new_window)
|
||||
|
||||
def test_closed_tab(self):
|
||||
with self.marionette.using_context("content"):
|
||||
tab = self.open_tab(self.open_tab_in_foreground)
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.close()
|
||||
new_tab = self.open_tab(focus=True)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
self.marionette.close()
|
||||
|
||||
# Check that only the content window is not available in both contexts
|
||||
for context in ("chrome", "content"):
|
||||
@ -88,22 +73,24 @@ class TestNoSuchWindowContent(WindowManagerMixin, MarionetteTestCase):
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
def test_closed_tab_while_in_frame(self):
|
||||
new_tab = self.open_tab()
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
||||
with self.marionette.using_context("content"):
|
||||
tab = self.open_tab(self.open_tab_in_foreground)
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.navigate(self.marionette.absolute_url("test_iframe.html"))
|
||||
frame = self.marionette.find_element(By.ID, "test_iframe")
|
||||
self.marionette.switch_to_frame(frame)
|
||||
self.marionette.close()
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.current_window_handle
|
||||
self.marionette.current_chrome_window_handle
|
||||
self.marionette.close()
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.current_window_handle
|
||||
self.marionette.current_chrome_window_handle
|
||||
|
||||
self.marionette.switch_to_window(self.start_tab)
|
||||
|
||||
with self.assertRaises(NoSuchWindowException):
|
||||
self.marionette.switch_to_window(tab)
|
||||
self.marionette.switch_to_window(new_tab)
|
||||
|
@ -11,7 +11,6 @@ const ServerSocket = CC(
|
||||
"nsIServerSocket",
|
||||
"initSpecialConnection");
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.import("chrome://marionette/content/assert.js");
|
||||
@ -74,7 +73,7 @@ class TCPListener {
|
||||
*/
|
||||
driverFactory() {
|
||||
MarionettePrefs.contentListener = false;
|
||||
return new GeckoDriver(Services.appinfo.ID, this);
|
||||
return new GeckoDriver(this);
|
||||
}
|
||||
|
||||
set acceptConnections(value) {
|
||||
|
@ -13,23 +13,43 @@ const {
|
||||
stack,
|
||||
TimeoutError,
|
||||
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
|
||||
const {truncate} = ChromeUtils.import("chrome://marionette/content/format.js", {});
|
||||
const {Log} = ChromeUtils.import("chrome://marionette/content/log.js", {});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", Log.get);
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"executeSoon",
|
||||
"DebounceCallback",
|
||||
"IdlePromise",
|
||||
"MessageManagerDestroyedPromise",
|
||||
"PollPromise",
|
||||
"Sleep",
|
||||
"TimedPromise",
|
||||
"waitForEvent",
|
||||
"waitForMessage",
|
||||
"waitForObserverTopic",
|
||||
];
|
||||
|
||||
const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
|
||||
|
||||
const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch a function to be executed on the main thread.
|
||||
*
|
||||
* @param {function} func
|
||||
* Function to be executed.
|
||||
*/
|
||||
function executeSoon(func) {
|
||||
if (typeof func != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
Services.tm.dispatchToMainThread(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback Condition
|
||||
*
|
||||
@ -72,14 +92,14 @@ const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
|
||||
* } else {
|
||||
* reject([]);
|
||||
* }
|
||||
* });
|
||||
* }, {timeout: 1000});
|
||||
*
|
||||
* @param {Condition} func
|
||||
* Function to run off the main thread.
|
||||
* @param {number=} [timeout=2000] timeout
|
||||
* Desired timeout. If 0 or less than the runtime evaluation
|
||||
* @param {number=} [timeout] timeout
|
||||
* Desired timeout if wanted. If 0 or less than the runtime evaluation
|
||||
* time of ``func``, ``func`` is guaranteed to run at least once.
|
||||
* The default is 2000 milliseconds.
|
||||
* Defaults to using no timeout.
|
||||
* @param {number=} [interval=10] interval
|
||||
* Duration between each poll of ``func`` in milliseconds.
|
||||
* Defaults to 10 milliseconds.
|
||||
@ -95,23 +115,30 @@ const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
|
||||
* @throws {RangeError}
|
||||
* If `timeout` or `interval` are not unsigned integers.
|
||||
*/
|
||||
function PollPromise(func, {timeout = 2000, interval = 10} = {}) {
|
||||
function PollPromise(func, {timeout = null, interval = 10} = {}) {
|
||||
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
if (typeof func != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (!(typeof timeout == "number" && typeof interval == "number")) {
|
||||
if (timeout != null && typeof timeout != "number") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if ((!Number.isInteger(timeout) || timeout < 0) ||
|
||||
if (typeof interval != "number") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if ((timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
|
||||
(!Number.isInteger(interval) || interval < 0)) {
|
||||
throw new RangeError();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = new Date().getTime();
|
||||
const end = start + timeout;
|
||||
let start, end;
|
||||
|
||||
if (Number.isInteger(timeout)) {
|
||||
start = new Date().getTime();
|
||||
end = start + timeout;
|
||||
}
|
||||
|
||||
let evalFn = () => {
|
||||
new Promise(func).then(resolve, rejected => {
|
||||
@ -119,9 +146,10 @@ function PollPromise(func, {timeout = 2000, interval = 10} = {}) {
|
||||
throw rejected;
|
||||
}
|
||||
|
||||
// return if timeout is 0, allowing |func| to be evaluated at
|
||||
// least once
|
||||
if (start == end || new Date().getTime() >= end) {
|
||||
// return if there is a timeout and set to 0,
|
||||
// allowing |func| to be evaluated at least once
|
||||
if (typeof end != "undefined" &&
|
||||
(start == end || new Date().getTime() >= end)) {
|
||||
resolve(rejected);
|
||||
}
|
||||
}).catch(reject);
|
||||
@ -351,3 +379,192 @@ class DebounceCallback {
|
||||
}
|
||||
}
|
||||
this.DebounceCallback = DebounceCallback;
|
||||
|
||||
/**
|
||||
* Wait for an event to be fired on a specified element.
|
||||
*
|
||||
* This method has been duplicated from BrowserTestUtils.jsm.
|
||||
*
|
||||
* Because this function is intended for testing, any error in checkFn
|
||||
* will cause the returned promise to be rejected instead of waiting for
|
||||
* the next event, since this is probably a bug in the test.
|
||||
*
|
||||
* Usage::
|
||||
*
|
||||
* let promiseEvent = waitForEvent(element, "eventName");
|
||||
* // Do some processing here that will cause the event to be fired
|
||||
* // ...
|
||||
* // Now wait until the Promise is fulfilled
|
||||
* let receivedEvent = await promiseEvent;
|
||||
*
|
||||
* The promise resolution/rejection handler for the returned promise is
|
||||
* guaranteed not to be called until the next event tick after the event
|
||||
* listener gets called, so that all other event listeners for the element
|
||||
* are executed before the handler is executed::
|
||||
*
|
||||
* let promiseEvent = waitForEvent(element, "eventName");
|
||||
* // Same event tick here.
|
||||
* await promiseEvent;
|
||||
* // Next event tick here.
|
||||
*
|
||||
* If some code, such like adding yet another event listener, needs to be
|
||||
* executed in the same event tick, use raw addEventListener instead and
|
||||
* place the code inside the event listener::
|
||||
*
|
||||
* element.addEventListener("load", () => {
|
||||
* // Add yet another event listener in the same event tick as the load
|
||||
* // event listener.
|
||||
* p = waitForEvent(element, "ready");
|
||||
* }, { once: true });
|
||||
*
|
||||
* @param {Element} subject
|
||||
* The element that should receive the event.
|
||||
* @param {string} eventName
|
||||
* Name of the event to listen to.
|
||||
* @param {Object=} options
|
||||
* Extra options.
|
||||
* @param {boolean=} options.capture
|
||||
* True to use a capturing listener.
|
||||
* @param {function(Event)=} options.checkFn
|
||||
* Called with the ``Event`` object as argument, should return ``true``
|
||||
* if the event is the expected one, or ``false`` if it should be
|
||||
* ignored and listening should continue. If not specified, the first
|
||||
* event with the specified name resolves the returned promise.
|
||||
* @param {boolean=} options.wantsUntrusted
|
||||
* True to receive synthetic events dispatched by web content.
|
||||
*
|
||||
* @return {Promise.<Event>}
|
||||
* Promise which resolves to the received ``Event`` object, or rejects
|
||||
* in case of a failure.
|
||||
*/
|
||||
function waitForEvent(subject, eventName,
|
||||
{capture = false, checkFn = null, wantsUntrusted = false} = {}) {
|
||||
if (subject == null || !("addEventListener" in subject)) {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (typeof eventName != "string") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (capture != null && typeof capture != "boolean") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (checkFn != null && typeof checkFn != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (wantsUntrusted != null && typeof wantsUntrusted != "boolean") {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
subject.addEventListener(eventName, function listener(event) {
|
||||
log.trace(`Received DOM event ${event.type} for ${event.target}`);
|
||||
try {
|
||||
if (checkFn && !checkFn(event)) {
|
||||
return;
|
||||
}
|
||||
subject.removeEventListener(eventName, listener, capture);
|
||||
executeSoon(() => resolve(event));
|
||||
} catch (ex) {
|
||||
try {
|
||||
subject.removeEventListener(eventName, listener, capture);
|
||||
} catch (ex2) {
|
||||
// Maybe the provided object does not support removeEventListener.
|
||||
}
|
||||
executeSoon(() => reject(ex));
|
||||
}
|
||||
}, capture, wantsUntrusted);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a message to be fired from a particular message manager.
|
||||
*
|
||||
* This method has been duplicated from BrowserTestUtils.jsm.
|
||||
*
|
||||
* @param {nsIMessageManager} messageManager
|
||||
* The message manager that should be used.
|
||||
* @param {string} messageName
|
||||
* The message to wait for.
|
||||
* @param {Object=} options
|
||||
* Extra options.
|
||||
* @param {function(Message)=} options.checkFn
|
||||
* Called with the ``Message`` object as argument, should return ``true``
|
||||
* if the message is the expected one, or ``false`` if it should be
|
||||
* ignored and listening should continue. If not specified, the first
|
||||
* message with the specified name resolves the returned promise.
|
||||
*
|
||||
* @return {Promise.<Object>}
|
||||
* Promise which resolves to the data property of the received
|
||||
* ``Message``.
|
||||
*/
|
||||
function waitForMessage(messageManager, messageName,
|
||||
{checkFn = undefined} = {}) {
|
||||
if (messageManager == null || !("addMessageListener" in messageManager)) {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (typeof messageName != "string") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (checkFn && typeof checkFn != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
messageManager.addMessageListener(messageName, function onMessage(msg) {
|
||||
log.trace(`Received ${messageName} for ${msg.target}`);
|
||||
if (checkFn && !checkFn(msg)) {
|
||||
return;
|
||||
}
|
||||
messageManager.removeMessageListener(messageName, onMessage);
|
||||
resolve(msg.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the specified observer topic to be observed.
|
||||
*
|
||||
* This method has been duplicated from TestUtils.jsm.
|
||||
*
|
||||
* Because this function is intended for testing, any error in checkFn
|
||||
* will cause the returned promise to be rejected instead of waiting for
|
||||
* the next notification, since this is probably a bug in the test.
|
||||
*
|
||||
* @param {string} topic
|
||||
* The topic to observe.
|
||||
* @param {Object=} options
|
||||
* Extra options.
|
||||
* @param {function(String,Object)=} options.checkFn
|
||||
* Called with ``subject``, and ``data`` as arguments, should return true
|
||||
* if the notification is the expected one, or false if it should be
|
||||
* ignored and listening should continue. If not specified, the first
|
||||
* notification for the specified topic resolves the returned promise.
|
||||
*
|
||||
* @return {Promise.<Array<String, Object>>}
|
||||
* Promise which resolves to an array of ``subject``, and ``data`` from
|
||||
* the observed notification.
|
||||
*/
|
||||
function waitForObserverTopic(topic, {checkFn = null} = {}) {
|
||||
if (typeof topic != "string") {
|
||||
throw new TypeError();
|
||||
}
|
||||
if (checkFn != null && typeof checkFn != "function") {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver(function observer(subject, topic, data) {
|
||||
log.trace(`Received observer notification ${topic}`);
|
||||
try {
|
||||
if (checkFn && !checkFn(subject, data)) {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
resolve({subject, data});
|
||||
} catch (ex) {
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
reject(ex);
|
||||
}
|
||||
}, topic);
|
||||
});
|
||||
}
|
||||
|
@ -2,15 +2,90 @@
|
||||
* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const {
|
||||
DebounceCallback,
|
||||
IdlePromise,
|
||||
PollPromise,
|
||||
Sleep,
|
||||
TimedPromise,
|
||||
waitForEvent,
|
||||
waitForMessage,
|
||||
waitForObserverTopic,
|
||||
} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
|
||||
|
||||
const DEFAULT_TIMEOUT = 2000;
|
||||
/**
|
||||
* Mimic a DOM node for listening for events.
|
||||
*/
|
||||
class MockElement {
|
||||
constructor() {
|
||||
this.capture = false;
|
||||
this.func = null;
|
||||
this.eventName = null;
|
||||
this.untrusted = false;
|
||||
}
|
||||
|
||||
addEventListener(name, func, capture, untrusted) {
|
||||
this.eventName = name;
|
||||
this.func = func;
|
||||
if (capture != null) {
|
||||
this.capture = capture;
|
||||
}
|
||||
if (untrusted != null) {
|
||||
this.untrusted = untrusted;
|
||||
}
|
||||
}
|
||||
|
||||
click() {
|
||||
if (this.func) {
|
||||
let details = {
|
||||
capture: this.capture,
|
||||
target: this,
|
||||
type: this.eventName,
|
||||
untrusted: this.untrusted,
|
||||
};
|
||||
this.func(details);
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListener(name, func) {
|
||||
this.capture = false;
|
||||
this.func = null;
|
||||
this.eventName = null;
|
||||
this.untrusted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimic a message manager for sending messages.
|
||||
*/
|
||||
class MessageManager {
|
||||
constructor() {
|
||||
this.func = null;
|
||||
this.message = null;
|
||||
}
|
||||
|
||||
addMessageListener(message, func) {
|
||||
this.func = func;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
removeMessageListener(message) {
|
||||
this.func = null;
|
||||
this.message = null;
|
||||
}
|
||||
|
||||
send(message, data) {
|
||||
if (this.func) {
|
||||
this.func({
|
||||
data,
|
||||
message,
|
||||
target: this,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics nsITimer, but instead of using a system clock you can
|
||||
@ -35,6 +110,23 @@ class MockTimer {
|
||||
}
|
||||
}
|
||||
|
||||
add_test(function test_executeSoon_callback() {
|
||||
// executeSoon() is already defined for xpcshell in head.js. As such import
|
||||
// our implementation into a custom namespace.
|
||||
let sync = {};
|
||||
ChromeUtils.import("chrome://marionette/content/sync.js", sync);
|
||||
|
||||
for (let func of ["foo", null, true, [], {}]) {
|
||||
Assert.throws(() => sync.executeSoon(func), /TypeError/);
|
||||
}
|
||||
|
||||
let a;
|
||||
sync.executeSoon(() => { a = 1; });
|
||||
executeSoon(() => equal(1, a));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_PollPromise_funcTypes() {
|
||||
for (let type of ["foo", 42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => new PollPromise(type), /TypeError/);
|
||||
@ -46,13 +138,15 @@ add_test(function test_PollPromise_funcTypes() {
|
||||
});
|
||||
|
||||
add_test(function test_PollPromise_timeoutTypes() {
|
||||
for (let timeout of ["foo", null, true, [], {}]) {
|
||||
for (let timeout of ["foo", true, [], {}]) {
|
||||
Assert.throws(() => new PollPromise(() => {}, {timeout}), /TypeError/);
|
||||
}
|
||||
for (let timeout of [1.2, -1]) {
|
||||
Assert.throws(() => new PollPromise(() => {}, {timeout}), /RangeError/);
|
||||
}
|
||||
new PollPromise(() => {}, {timeout: 42});
|
||||
for (let timeout of [null, undefined, 42]) {
|
||||
new PollPromise(resolve => resolve(1), {timeout});
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
@ -75,18 +169,6 @@ add_task(async function test_PollPromise_retvalTypes() {
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_PollPromise_timeoutElapse() {
|
||||
let nevals = 0;
|
||||
let start = new Date().getTime();
|
||||
await new PollPromise((resolve, reject) => {
|
||||
++nevals;
|
||||
reject();
|
||||
});
|
||||
let end = new Date().getTime();
|
||||
greaterOrEqual((end - start), DEFAULT_TIMEOUT);
|
||||
greaterOrEqual(nevals, 15);
|
||||
});
|
||||
|
||||
add_task(async function test_PollPromise_rethrowError() {
|
||||
let nevals = 0;
|
||||
let err;
|
||||
@ -103,6 +185,15 @@ add_task(async function test_PollPromise_rethrowError() {
|
||||
});
|
||||
|
||||
add_task(async function test_PollPromise_noTimeout() {
|
||||
let nevals = 0;
|
||||
await new PollPromise((resolve, reject) => {
|
||||
++nevals;
|
||||
nevals < 100 ? reject() : resolve();
|
||||
});
|
||||
equal(100, nevals);
|
||||
});
|
||||
|
||||
add_task(async function test_PollPromise_zeroTimeout() {
|
||||
// run at least once when timeout is 0
|
||||
let nevals = 0;
|
||||
let start = new Date().getTime();
|
||||
@ -112,10 +203,10 @@ add_task(async function test_PollPromise_noTimeout() {
|
||||
}, {timeout: 0});
|
||||
let end = new Date().getTime();
|
||||
equal(1, nevals);
|
||||
less((end - start), DEFAULT_TIMEOUT);
|
||||
less((end - start), 500);
|
||||
});
|
||||
|
||||
add_task(async function test_PollPromise_timeout() {
|
||||
add_task(async function test_PollPromise_timeoutElapse() {
|
||||
let nevals = 0;
|
||||
let start = new Date().getTime();
|
||||
await new PollPromise((resolve, reject) => {
|
||||
@ -123,7 +214,7 @@ add_task(async function test_PollPromise_timeout() {
|
||||
reject();
|
||||
}, {timeout: 100});
|
||||
let end = new Date().getTime();
|
||||
greater(nevals, 1);
|
||||
lessOrEqual(nevals, 11);
|
||||
greaterOrEqual((end - start), 100);
|
||||
});
|
||||
|
||||
@ -213,3 +304,155 @@ add_task(async function test_DebounceCallback_repeatedCallback() {
|
||||
equal(ncalls, 1);
|
||||
ok(debouncer.timer.cancelled);
|
||||
});
|
||||
|
||||
add_task(async function test_waitForEvent_subjectAndEventNameTypes() {
|
||||
let element = new MockElement();
|
||||
|
||||
for (let subject of ["foo", 42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(subject, "click"), /TypeError/);
|
||||
}
|
||||
|
||||
for (let eventName of [42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(element, eventName), /TypeError/);
|
||||
}
|
||||
|
||||
let clicked = waitForEvent(element, "click");
|
||||
element.click();
|
||||
let event = await clicked;
|
||||
equal(element, event.target);
|
||||
});
|
||||
|
||||
add_task(async function test_waitForEvent_captureTypes() {
|
||||
let element = new MockElement();
|
||||
|
||||
for (let capture of ["foo", 42, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(
|
||||
element, "click", {capture}), /TypeError/);
|
||||
}
|
||||
|
||||
for (let capture of [null, undefined, false, true]) {
|
||||
let expected_capture = (capture == null) ? false : capture;
|
||||
|
||||
element = new MockElement();
|
||||
let clicked = waitForEvent(element, "click", {capture});
|
||||
element.click();
|
||||
let event = await clicked;
|
||||
equal(element, event.target);
|
||||
equal(expected_capture, event.capture);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_waitForEvent_checkFnTypes() {
|
||||
let element = new MockElement();
|
||||
|
||||
for (let checkFn of ["foo", 42, true, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(
|
||||
element, "click", {checkFn}), /TypeError/);
|
||||
}
|
||||
|
||||
let count;
|
||||
for (let checkFn of [null, undefined, event => count++ > 0]) {
|
||||
let expected_count = (checkFn == null) ? 0 : 2;
|
||||
count = 0;
|
||||
|
||||
element = new MockElement();
|
||||
let clicked = waitForEvent(element, "click", {checkFn});
|
||||
element.click();
|
||||
element.click();
|
||||
let event = await clicked;
|
||||
equal(element, event.target);
|
||||
equal(expected_count, count);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_waitForEvent_wantsUntrustedTypes() {
|
||||
let element = new MockElement();
|
||||
|
||||
for (let wantsUntrusted of ["foo", 42, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(
|
||||
element, "click", {wantsUntrusted}), /TypeError/);
|
||||
}
|
||||
|
||||
for (let wantsUntrusted of [null, undefined, false, true]) {
|
||||
let expected_untrusted = (wantsUntrusted == null) ? false : wantsUntrusted;
|
||||
|
||||
element = new MockElement();
|
||||
let clicked = waitForEvent(element, "click", {wantsUntrusted});
|
||||
element.click();
|
||||
let event = await clicked;
|
||||
equal(element, event.target);
|
||||
equal(expected_untrusted, event.untrusted);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_waitForMessage_messageManagerAndMessageTypes() {
|
||||
let messageManager = new MessageManager();
|
||||
|
||||
for (let manager of ["foo", 42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => waitForMessage(manager, "message"), /TypeError/);
|
||||
}
|
||||
|
||||
for (let message of [42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => waitForEvent(messageManager, message), /TypeError/);
|
||||
}
|
||||
|
||||
let data = {"foo": "bar"};
|
||||
let sent = waitForMessage(messageManager, "message");
|
||||
messageManager.send("message", data);
|
||||
equal(data, await sent);
|
||||
});
|
||||
|
||||
add_task(async function test_waitForMessage_checkFnTypes() {
|
||||
let messageManager = new MessageManager();
|
||||
|
||||
for (let checkFn of ["foo", 42, true, [], {}]) {
|
||||
Assert.throws(() => waitForMessage(
|
||||
messageManager, "message", {checkFn}), /TypeError/);
|
||||
}
|
||||
|
||||
let data1 = {"fo": "bar"};
|
||||
let data2 = {"foo": "bar"};
|
||||
|
||||
for (let checkFn of [null, undefined, msg => "foo" in msg.data]) {
|
||||
let expected_data = (checkFn == null) ? data1 : data2;
|
||||
|
||||
messageManager = new MessageManager();
|
||||
let sent = waitForMessage(messageManager, "message", {checkFn});
|
||||
messageManager.send("message", data1);
|
||||
messageManager.send("message", data2);
|
||||
equal(expected_data, await sent);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_waitForObserverTopic_topicTypes() {
|
||||
for (let topic of [42, null, undefined, true, [], {}]) {
|
||||
Assert.throws(() => waitForObserverTopic(topic), /TypeError/);
|
||||
}
|
||||
|
||||
let data = {"foo": "bar"};
|
||||
let sent = waitForObserverTopic("message");
|
||||
Services.obs.notifyObservers(this, "message", data);
|
||||
let result = await sent;
|
||||
equal(this, result.subject);
|
||||
equal(data, result.data);
|
||||
});
|
||||
|
||||
add_task(async function test_waitForObserverTopic_checkFnTypes() {
|
||||
for (let checkFn of ["foo", 42, true, [], {}]) {
|
||||
Assert.throws(() => waitForObserverTopic(
|
||||
"message", {checkFn}), /TypeError/);
|
||||
}
|
||||
|
||||
let data1 = {"fo": "bar"};
|
||||
let data2 = {"foo": "bar"};
|
||||
|
||||
for (let checkFn of [null, undefined, (subject, data) => data == data2]) {
|
||||
let expected_data = (checkFn == null) ? data1 : data2;
|
||||
|
||||
let sent = waitForObserverTopic("message");
|
||||
Services.obs.notifyObservers(this, "message", data1);
|
||||
Services.obs.notifyObservers(this, "message", data2);
|
||||
let result = await sent;
|
||||
equal(expected_data, result.data);
|
||||
}
|
||||
});
|
||||
|
@ -10,14 +10,17 @@ const CC = Components.Constructor;
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
|
||||
const {StreamUtils} =
|
||||
ChromeUtils.import("chrome://marionette/content/stream-utils.js", {});
|
||||
const {Packet, JSONPacket, BulkPacket} =
|
||||
ChromeUtils.import("chrome://marionette/content/packets.js", {});
|
||||
|
||||
const executeSoon = function(func) {
|
||||
Services.tm.dispatchToMainThread(func);
|
||||
};
|
||||
const {
|
||||
StreamUtils,
|
||||
} = ChromeUtils.import("chrome://marionette/content/stream-utils.js", {});
|
||||
const {
|
||||
BulkPacket,
|
||||
JSONPacket,
|
||||
Packet,
|
||||
} = ChromeUtils.import("chrome://marionette/content/packets.js", {});
|
||||
const {
|
||||
executeSoon,
|
||||
} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
|
||||
|
||||
const flags = {wantVerbose: false, wantLogging: false};
|
||||
|
||||
|
@ -80,6 +80,9 @@ class WebPlatformTestsRunnerSetup(MozbuildObject):
|
||||
if kwargs["log_mach_screenshot"] is None:
|
||||
kwargs["log_mach_screenshot"] = True
|
||||
|
||||
if kwargs["lsan_dir"] is None:
|
||||
kwargs["lsan_dir"] = os.path.join(self.topsrcdir, "build", "sanitizers")
|
||||
|
||||
kwargs["capture_stdio"] = True
|
||||
|
||||
return kwargs
|
||||
|
@ -1,5 +1,4 @@
|
||||
[promise.py]
|
||||
expected: TIMEOUT
|
||||
[test_promise_timeout]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -74,6 +74,7 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
|
||||
"certutil_binary": kwargs["certutil_binary"],
|
||||
"ca_certificate_path": config.ssl_config["ca_cert_path"],
|
||||
"e10s": kwargs["gecko_e10s"],
|
||||
"lsan_dir": kwargs["lsan_dir"],
|
||||
"stackfix_dir": kwargs["stackfix_dir"],
|
||||
"binary_args": kwargs["binary_args"],
|
||||
"timeout_multiplier": get_timeout_multiplier(test_type,
|
||||
@ -167,7 +168,7 @@ class FirefoxBrowser(Browser):
|
||||
|
||||
def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
|
||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||
ca_certificate_path=None, e10s=False, stackfix_dir=None,
|
||||
ca_certificate_path=None, e10s=False, lsan_dir=None, stackfix_dir=None,
|
||||
binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
|
||||
stylo_threads=1, chaos_mode_flags=None, config=None, headless=None, **kwargs):
|
||||
Browser.__init__(self, logger)
|
||||
@ -196,6 +197,7 @@ class FirefoxBrowser(Browser):
|
||||
self.init_timeout = self.init_timeout * timeout_multiplier
|
||||
|
||||
self.asan = asan
|
||||
self.lsan_dir = lsan_dir
|
||||
self.lsan_allowed = None
|
||||
self.lsan_max_stack_depth = None
|
||||
self.mozleak_allowed = None
|
||||
@ -237,7 +239,7 @@ class FirefoxBrowser(Browser):
|
||||
env = test_environment(xrePath=os.path.dirname(self.binary),
|
||||
debugger=self.debug_info is not None,
|
||||
log=self.logger,
|
||||
lsanPath=self.prefs_root)
|
||||
lsanPath=self.lsan_dir)
|
||||
|
||||
env["STYLO_THREADS"] = str(self.stylo_threads)
|
||||
if self.chaos_mode_flags is not None:
|
||||
|
@ -251,6 +251,8 @@ scheme host and port.""")
|
||||
help="Run tests without electrolysis preferences")
|
||||
gecko_group.add_argument("--stackfix-dir", dest="stackfix_dir", action="store",
|
||||
help="Path to directory containing assertion stack fixing scripts")
|
||||
gecko_group.add_argument("--lsan-dir", dest="lsan_dir", action="store",
|
||||
help="Path to directory containing LSAN suppressions file")
|
||||
gecko_group.add_argument("--setpref", dest="extra_prefs", action='append',
|
||||
default=[], metavar="PREF=VALUE",
|
||||
help="Defines an extra user preference (overrides those in prefs_root)")
|
||||
@ -525,6 +527,9 @@ def check_args(kwargs):
|
||||
if kwargs["reftest_internal"] is None:
|
||||
kwargs["reftest_internal"] = True
|
||||
|
||||
if kwargs["lsan_dir"] is None:
|
||||
kwargs["lsan_dir"] = kwargs["prefs_root"]
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
2
third_party/rust/encoding_rs/Cargo.toml
vendored
2
third_party/rust/encoding_rs/Cargo.toml
vendored
@ -12,7 +12,7 @@
|
||||
|
||||
[package]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
authors = ["Henri Sivonen <hsivonen@hsivonen.fi>"]
|
||||
description = "A Gecko-oriented implementation of the Encoding Standard"
|
||||
homepage = "https://docs.rs/encoding_rs/"
|
||||
|
28
third_party/rust/encoding_rs/README.md
vendored
28
third_party/rust/encoding_rs/README.md
vendored
@ -81,16 +81,24 @@ For decoding character encodings that occur in email, use the
|
||||
[`charset`](https://crates.io/crates/charset) crate instead of using this
|
||||
one directly. (It wraps this crate and adds UTF-7 decoding.)
|
||||
|
||||
## Windows Code Page Identifier Mappings
|
||||
|
||||
For mappings to and from Windows code page identifiers, use the
|
||||
[`codepage`](https://crates.io/crates/codepage) crate.
|
||||
|
||||
## Licensing
|
||||
|
||||
Please see the file named
|
||||
[COPYRIGHT](https://github.com/hsivonen/encoding_rs/blob/master/COPYRIGHT).
|
||||
|
||||
## API Documentation
|
||||
## Documentation
|
||||
|
||||
Generated [API documentation](https://docs.rs/encoding_rs/) is available
|
||||
online.
|
||||
|
||||
There is a [long-form write-up](https://hsivonen.fi/encoding_rs/) about the
|
||||
design and internals of the crate.
|
||||
|
||||
## C and C++ bindings
|
||||
|
||||
An FFI layer for encoding_rs is available as a
|
||||
@ -103,6 +111,9 @@ For the Gecko context, there's a
|
||||
|
||||
These bindings do not cover the `mem` module.
|
||||
|
||||
There's a [write-up](https://hsivonen.fi/modern-cpp-in-rust/) about the C++
|
||||
wrappers.
|
||||
|
||||
## Sample programs
|
||||
|
||||
* [Rust](https://github.com/hsivonen/recode_rs)
|
||||
@ -326,6 +337,8 @@ To regenerate the generated code:
|
||||
* Have Python 2 installed.
|
||||
* Clone [`https://github.com/hsivonen/encoding_c`](https://github.com/hsivonen/encoding_c)
|
||||
next to the `encoding_rs` directory.
|
||||
* Clone [`https://github.com/hsivonen/codepage`](https://github.com/hsivonen/codepage)
|
||||
next to the `encoding_rs` directory.
|
||||
* Clone [`https://github.com/whatwg/encoding`](https://github.com/whatwg/encoding)
|
||||
next to the `encoding_rs` directory.
|
||||
* Checkout revision `f381389` of the `encoding` repo.
|
||||
@ -365,15 +378,20 @@ To regenerate the generated code:
|
||||
- [ ] ~Investigate Björn Höhrmann's lookup table acceleration for UTF-8 as
|
||||
adapted to Rust in rust-encoding.~
|
||||
- [x] Add actually fast CJK encode options.
|
||||
- [ ] Investigate [Bob Steagall's lookup table acceleration for UTF-8](https://github.com/BobSteagall/CppNow2018/blob/master/FastConversionFromUTF-8/Fast%20Conversion%20From%20UTF-8%20with%20C%2B%2B%2C%20DFAs%2C%20and%20SSE%20Intrinsics%20-%20Bob%20Steagall%20-%20C%2B%2BNow%202018.pdf).
|
||||
- [ ] ~Investigate [Bob Steagall's lookup table acceleration for UTF-8](https://github.com/BobSteagall/CppNow2018/blob/master/FastConversionFromUTF-8/Fast%20Conversion%20From%20UTF-8%20with%20C%2B%2B%2C%20DFAs%2C%20and%20SSE%20Intrinsics%20-%20Bob%20Steagall%20-%20C%2B%2BNow%202018.pdf).~
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.8.14
|
||||
|
||||
* Made UTF-16 to UTF-8 encode conversion fill the output buffer as
|
||||
closely as possible.
|
||||
|
||||
### 0.8.13
|
||||
|
||||
* Made the UTF-8 to UTF-16 compare the number of code units written with the
|
||||
length of the right slice (the output slice) to fix a panic introduced in
|
||||
0.8.11.
|
||||
* Made the UTF-8 to UTF-16 decoder compare the number of code units written
|
||||
with the length of the right slice (the output slice) to fix a panic
|
||||
introduced in 0.8.11.
|
||||
|
||||
### 0.8.12
|
||||
|
||||
|
@ -22,6 +22,10 @@ if not os.path.isfile("../encoding_c/src/lib.rs"):
|
||||
sys.stderr.write("This script also writes the generated parts of the encoding_c crate and needs a clone of https://github.com/hsivonen/encoding_c next to the encoding_rs directory.\n");
|
||||
sys.exit(-1)
|
||||
|
||||
if not os.path.isfile("../codepage/src/lib.rs"):
|
||||
sys.stderr.write("This script also writes the generated parts of the codepage crate and needs a clone of https://github.com/hsivonen/codepage next to the encoding_rs directory.\n");
|
||||
sys.exit(-1)
|
||||
|
||||
def cmp_from_end(one, other):
|
||||
c = cmp(len(one), len(other))
|
||||
if c != 0:
|
||||
@ -119,8 +123,6 @@ single_byte = []
|
||||
|
||||
multi_byte = []
|
||||
|
||||
code_pages = []
|
||||
|
||||
def to_camel_name(name):
|
||||
if name == u"iso-8859-8-i":
|
||||
return u"Iso8I"
|
||||
@ -137,6 +139,52 @@ def to_snake_name(name):
|
||||
def to_dom_name(name):
|
||||
return name
|
||||
|
||||
# Guestimate based on
|
||||
# https://w3techs.com/technologies/overview/character_encoding/all
|
||||
# whose methodology is known to be bogus, but the results are credible for
|
||||
# this purpose. UTF-16LE lifted up due to prevalence on Windows and
|
||||
# "ANSI codepages" prioritized.
|
||||
encodings_by_code_page_frequency = [
|
||||
"UTF-8",
|
||||
"UTF-16LE",
|
||||
"windows-1252",
|
||||
"windows-1251",
|
||||
"GBK",
|
||||
"Shift_JIS",
|
||||
"EUC-KR",
|
||||
"windows-1250",
|
||||
"windows-1256",
|
||||
"windows-1254",
|
||||
"Big5",
|
||||
"windows-874",
|
||||
"windows-1255",
|
||||
"windows-1253",
|
||||
"windows-1257",
|
||||
"windows-1258",
|
||||
"EUC-JP",
|
||||
"ISO-8859-2",
|
||||
"ISO-8859-15",
|
||||
"ISO-8859-7",
|
||||
"KOI8-R",
|
||||
"gb18030",
|
||||
"ISO-8859-5",
|
||||
"ISO-8859-8-I",
|
||||
"ISO-8859-4",
|
||||
"ISO-8859-6",
|
||||
"ISO-2022-JP",
|
||||
"KOI8-U",
|
||||
"ISO-8859-13",
|
||||
"ISO-8859-3",
|
||||
"UTF-16BE",
|
||||
"IBM866",
|
||||
"ISO-8859-10",
|
||||
"ISO-8859-8",
|
||||
"macintosh",
|
||||
"x-mac-cyrillic",
|
||||
"ISO-8859-14",
|
||||
"ISO-8859-16",
|
||||
]
|
||||
|
||||
encodings_by_code_page = {
|
||||
932: "Shift_JIS",
|
||||
936: "GBK",
|
||||
@ -185,18 +233,36 @@ for code_page, encoding in encodings_by_code_page.iteritems():
|
||||
|
||||
encoding_by_alias_code_page = {
|
||||
951: "Big5",
|
||||
10007: "x-mac-cyrillic",
|
||||
20936: "GBK",
|
||||
20949: "EUC-KR",
|
||||
21010: "UTF-16LE", # Undocumented; needed by calamine for Excel compat
|
||||
28591: "windows-1252",
|
||||
28599: "windows-1254",
|
||||
28601: "windows-847",
|
||||
28601: "windows-874",
|
||||
50220: "ISO-2022-JP",
|
||||
50222: "ISO-2022-JP",
|
||||
50225: "replacement", # ISO-2022-KR
|
||||
50227: "replacement", # ISO-2022-CN
|
||||
51949: "EUC-JP",
|
||||
51936: "GBK",
|
||||
51949: "EUC-KR",
|
||||
52936: "replacement", # HZ
|
||||
}
|
||||
|
||||
code_pages = []
|
||||
|
||||
for name in encodings_by_code_page_frequency:
|
||||
code_pages.append(code_pages_by_encoding[name])
|
||||
|
||||
encodings_by_code_page.update(encoding_by_alias_code_page)
|
||||
|
||||
temp_keys = encodings_by_code_page.keys()
|
||||
temp_keys.sort()
|
||||
for code_page in temp_keys:
|
||||
if not code_page in code_pages:
|
||||
code_pages.append(code_page)
|
||||
|
||||
# The position in the index (0 is the first index entry,
|
||||
# i.e. byte value 0x80) that starts the longest run of
|
||||
# consecutive code points. Must not be in the first
|
||||
@ -1806,4 +1872,71 @@ for pointer in range(0, len(index)):
|
||||
jis0212_in_ref_file.write(u"\uFFFD\n".encode("utf-8"))
|
||||
jis0212_in_ref_file.close()
|
||||
|
||||
(codepage_begin, codepage_end) = read_non_generated("../codepage/src/lib.rs")
|
||||
|
||||
codepage_file = open("../codepage/src/lib.rs", "w")
|
||||
|
||||
codepage_file.write(codepage_begin)
|
||||
codepage_file.write("""
|
||||
// Instead, please regenerate using generate-encoding-data.py
|
||||
|
||||
/// Supported code page numbers in estimated order of usage frequency
|
||||
static CODE_PAGES: [u16; %d] = [
|
||||
""" % len(code_pages))
|
||||
|
||||
for code_page in code_pages:
|
||||
codepage_file.write(" %d,\n" % code_page)
|
||||
|
||||
codepage_file.write("""];
|
||||
|
||||
/// Encodings corresponding to the code page numbers in the same order
|
||||
static ENCODINGS: [&'static Encoding; %d] = [
|
||||
""" % len(code_pages))
|
||||
|
||||
for code_page in code_pages:
|
||||
name = encodings_by_code_page[code_page]
|
||||
codepage_file.write(" &%s_INIT,\n" % to_constant_name(name))
|
||||
|
||||
codepage_file.write("""];
|
||||
|
||||
""")
|
||||
|
||||
codepage_file.write(codepage_end)
|
||||
codepage_file.close()
|
||||
|
||||
(codepage_test_begin, codepage_test_end) = read_non_generated("../codepage/src/tests.rs")
|
||||
|
||||
codepage_test_file = open("../codepage/src/tests.rs", "w")
|
||||
|
||||
codepage_test_file.write(codepage_test_begin)
|
||||
codepage_test_file.write("""
|
||||
// Instead, please regenerate using generate-encoding-data.py
|
||||
|
||||
#[test]
|
||||
fn test_to_encoding() {
|
||||
assert_eq!(to_encoding(0), None);
|
||||
|
||||
""")
|
||||
|
||||
for code_page in code_pages:
|
||||
codepage_test_file.write(" assert_eq!(to_encoding(%d), Some(%s));\n" % (code_page, to_constant_name(encodings_by_code_page[code_page])))
|
||||
|
||||
codepage_test_file.write("""}
|
||||
|
||||
#[test]
|
||||
fn test_from_encoding() {
|
||||
""")
|
||||
|
||||
for name in preferred:
|
||||
if code_pages_by_encoding.has_key(name):
|
||||
codepage_test_file.write(" assert_eq!(from_encoding(%s), Some(%d));\n" % (to_constant_name(name), code_pages_by_encoding[name]))
|
||||
else:
|
||||
codepage_test_file.write(" assert_eq!(from_encoding(%s), None);\n" % to_constant_name(name))
|
||||
|
||||
codepage_test_file.write("""}
|
||||
""")
|
||||
|
||||
codepage_test_file.write(codepage_test_end)
|
||||
codepage_test_file.close()
|
||||
|
||||
subprocess.call(["cargo", "fmt"])
|
||||
|
19
third_party/rust/encoding_rs/src/ascii.rs
vendored
19
third_party/rust/encoding_rs/src/ascii.rs
vendored
@ -31,7 +31,7 @@
|
||||
))]
|
||||
use simd_funcs::*;
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "simd-accel")] {
|
||||
#[allow(unused_imports)]
|
||||
use ::std::intrinsics::unlikely;
|
||||
@ -90,10 +90,7 @@ macro_rules! ascii_alu {
|
||||
$src_unit:ty,
|
||||
$dst_unit:ty,
|
||||
$stride_fn:ident) => {
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(never_loop, cast_ptr_alignment)
|
||||
)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(never_loop, cast_ptr_alignment))]
|
||||
#[inline(always)]
|
||||
pub unsafe fn $name(
|
||||
src: *const $src_unit,
|
||||
@ -186,11 +183,7 @@ macro_rules! basic_latin_alu {
|
||||
$stride_fn:ident) => {
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
never_loop,
|
||||
cast_ptr_alignment,
|
||||
cast_lossless
|
||||
)
|
||||
allow(never_loop, cast_ptr_alignment, cast_lossless)
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub unsafe fn $name(
|
||||
@ -285,11 +278,7 @@ macro_rules! latin1_alu {
|
||||
($name:ident, $src_unit:ty, $dst_unit:ty, $stride_fn:ident) => {
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
never_loop,
|
||||
cast_ptr_alignment,
|
||||
cast_lossless
|
||||
)
|
||||
allow(never_loop, cast_ptr_alignment, cast_lossless)
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub unsafe fn $name(src: *const $src_unit, dst: *mut $dst_unit, len: usize) {
|
||||
|
50
third_party/rust/encoding_rs/src/data.rs
vendored
50
third_party/rust/encoding_rs/src/data.rs
vendored
@ -29498,10 +29498,7 @@ pub static JIS0208_RANGE_TRIPLES: [u16; 54] = [
|
||||
0x29DC, 0x000A, 0x2170, 0x29E6, 0x000A, 0x2160,
|
||||
];
|
||||
|
||||
#[cfg(all(
|
||||
feature = "less-slow-kanji-encode",
|
||||
not(feature = "fast-kanji-encode")
|
||||
))]
|
||||
#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
|
||||
static JIS0208_LEVEL1_KANJI_CODE_POINTS: [u16; 2965] = [
|
||||
0x4E00, 0x4E01, 0x4E03, 0x4E07, 0x4E08, 0x4E09, 0x4E0A, 0x4E0B, 0x4E0D, 0x4E0E, 0x4E11, 0x4E14,
|
||||
0x4E16, 0x4E18, 0x4E19, 0x4E1E, 0x4E21, 0x4E26, 0x4E2D, 0x4E32, 0x4E38, 0x4E39, 0x4E3B, 0x4E43,
|
||||
@ -29753,10 +29750,7 @@ static JIS0208_LEVEL1_KANJI_CODE_POINTS: [u16; 2965] = [
|
||||
0x9F8D,
|
||||
];
|
||||
|
||||
#[cfg(all(
|
||||
feature = "less-slow-kanji-encode",
|
||||
not(feature = "fast-kanji-encode")
|
||||
))]
|
||||
#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
|
||||
static JIS0208_LEVEL1_KANJI_SHIFT_JIS_BYTES: [[u8; 2]; 2965] = [
|
||||
[0x88, 0xEA],
|
||||
[0x92, 0x9A],
|
||||
@ -113967,10 +113961,7 @@ pub fn jis0208_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
Some((lead | 0x80, trail))
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "less-slow-kanji-encode",
|
||||
feature = "fast-kanji-encode"
|
||||
))]
|
||||
#[cfg(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode"))]
|
||||
#[inline(always)]
|
||||
fn shift_jis_to_euc_jp(tuple: (u8, u8)) -> (u8, u8) {
|
||||
let (shift_jis_lead, shift_jis_trail) = tuple;
|
||||
@ -114012,10 +114003,7 @@ pub fn jis0208_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
Some(shift_jis_to_euc_jp((lead, trail)))
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "less-slow-kanji-encode",
|
||||
feature = "fast-kanji-encode"
|
||||
))]
|
||||
#[cfg(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode"))]
|
||||
#[inline(always)]
|
||||
fn shift_jis_to_iso_2022_jp(tuple: (u8, u8)) -> (u8, u8) {
|
||||
let (shift_jis_lead, shift_jis_trail) = tuple;
|
||||
@ -114057,10 +114045,7 @@ pub fn jis0208_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
Some(shift_jis_to_iso_2022_jp((lead, trail)))
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "less-slow-kanji-encode",
|
||||
feature = "fast-kanji-encode"
|
||||
)))]
|
||||
#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
|
||||
@ -114073,10 +114058,7 @@ pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "less-slow-kanji-encode",
|
||||
not(feature = "fast-kanji-encode")
|
||||
))]
|
||||
#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
match JIS0208_LEVEL1_KANJI_CODE_POINTS.binary_search(&bmp) {
|
||||
@ -114088,10 +114070,7 @@ pub fn jis0208_level1_kanji_shift_jis_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "less-slow-kanji-encode",
|
||||
feature = "fast-kanji-encode"
|
||||
)))]
|
||||
#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
|
||||
@ -114101,19 +114080,13 @@ pub fn jis0208_level1_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "less-slow-kanji-encode",
|
||||
not(feature = "fast-kanji-encode")
|
||||
))]
|
||||
#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_euc_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
jis0208_level1_kanji_shift_jis_encode(bmp).map(shift_jis_to_euc_jp)
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "less-slow-kanji-encode",
|
||||
feature = "fast-kanji-encode"
|
||||
)))]
|
||||
#[cfg(not(any(feature = "less-slow-kanji-encode", feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
position(&JIS0208_LEVEL1_KANJI[..], bmp).map(|kanji_pointer| {
|
||||
@ -114123,10 +114096,7 @@ pub fn jis0208_level1_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "less-slow-kanji-encode",
|
||||
not(feature = "fast-kanji-encode")
|
||||
))]
|
||||
#[cfg(all(feature = "less-slow-kanji-encode", not(feature = "fast-kanji-encode")))]
|
||||
#[inline(always)]
|
||||
pub fn jis0208_level1_kanji_iso_2022_jp_encode(bmp: u16) -> Option<(u8, u8)> {
|
||||
jis0208_level1_kanji_shift_jis_encode(bmp).map(shift_jis_to_iso_2022_jp)
|
||||
|
2
third_party/rust/encoding_rs/src/handles.rs
vendored
2
third_party/rust/encoding_rs/src/handles.rs
vendored
@ -282,7 +282,7 @@ fn convert_unaligned_utf16_to_utf8<E: Endian>(
|
||||
unit
|
||||
}
|
||||
CopyAsciiResult::Stop(read_written) => {
|
||||
return (src_pos + read_written, dst_pos + read_written, false)
|
||||
return (src_pos + read_written, dst_pos + read_written, false);
|
||||
}
|
||||
};
|
||||
if dst_pos >= dst_len_minus_three {
|
||||
|
10
third_party/rust/encoding_rs/src/iso_2022_jp.rs
vendored
10
third_party/rust/encoding_rs/src/iso_2022_jp.rs
vendored
@ -367,10 +367,7 @@ fn is_kanji_mapped(bmp: u16) -> bool {
|
||||
#[cfg(not(feature = "fast-kanji-encode"))]
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
if_let_redundant_pattern_matching,
|
||||
if_same_then_else
|
||||
)
|
||||
allow(if_let_redundant_pattern_matching, if_same_then_else)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn is_kanji_mapped(bmp: u16) -> bool {
|
||||
@ -391,10 +388,7 @@ fn is_kanji_mapped(bmp: u16) -> bool {
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
if_let_redundant_pattern_matching,
|
||||
if_same_then_else
|
||||
)
|
||||
allow(if_let_redundant_pattern_matching, if_same_then_else)
|
||||
)]
|
||||
fn is_mapped_for_two_byte_encode(bmp: u16) -> bool {
|
||||
// The code below uses else after return to
|
||||
|
46
third_party/rust/encoding_rs/src/lib.rs
vendored
46
third_party/rust/encoding_rs/src/lib.rs
vendored
@ -9,13 +9,9 @@
|
||||
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(
|
||||
doc_markdown,
|
||||
inline_always,
|
||||
new_ret_no_self
|
||||
)
|
||||
allow(doc_markdown, inline_always, new_ret_no_self)
|
||||
)]
|
||||
#![doc(html_root_url = "https://docs.rs/encoding_rs/0.8.13")]
|
||||
#![doc(html_root_url = "https://docs.rs/encoding_rs/0.8.14")]
|
||||
|
||||
//! encoding_rs is a Gecko-oriented Free Software / Open Source implementation
|
||||
//! of the [Encoding Standard](https://encoding.spec.whatwg.org/) in Rust.
|
||||
@ -32,6 +28,9 @@
|
||||
//! [_UTF-16LE, UTF-16BE and Unicode Encoding Schemes_](#utf-16le-utf-16be-and-unicode-encoding-schemes),
|
||||
//! [_ISO-8859-1_](#iso-8859-1) and [_Web / Browser Focus_](#web--browser-focus) below.
|
||||
//!
|
||||
//! There is a [long-form write-up](https://hsivonen.fi/encoding_rs/) about the
|
||||
//! design and internals of the crate.
|
||||
//!
|
||||
//! # Availability
|
||||
//!
|
||||
//! The code is available under the
|
||||
@ -240,7 +239,9 @@
|
||||
//! way will find encoding_rs useful. While encoding_rs does not try to match
|
||||
//! Windows behavior, many of the encodings are close enough to legacy
|
||||
//! encodings implemented by Windows that applications that need to consume
|
||||
//! data in legacy Windows encodins may find encoding_rs useful.
|
||||
//! data in legacy Windows encodins may find encoding_rs useful. The
|
||||
//! [codepage](https://crates.io/crates/codepage) crate maps from Windows
|
||||
//! code page identifiers onto encoding_rs `Encoding`s and vice versa.
|
||||
//!
|
||||
//! For decoding email, UTF-7 support is needed (unfortunately) in additition
|
||||
//! to the encodings defined in the Encoding Standard. The
|
||||
@ -664,10 +665,7 @@
|
||||
//! See the section [_UTF-16LE, UTF-16BE and Unicode Encoding Schemes_](#utf-16le-utf-16be-and-unicode-encoding-schemes)
|
||||
//! for discussion about the UTF-16 family.
|
||||
|
||||
#![cfg_attr(
|
||||
feature = "simd-accel",
|
||||
feature(platform_intrinsics, core_intrinsics)
|
||||
)]
|
||||
#![cfg_attr(feature = "simd-accel", feature(platform_intrinsics, core_intrinsics))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
@ -3653,7 +3651,7 @@ impl Decoder {
|
||||
| DecoderLifeCycle::AtUtf8Start
|
||||
| DecoderLifeCycle::AtUtf16LeStart
|
||||
| DecoderLifeCycle::AtUtf16BeStart => {
|
||||
return self.variant.max_utf8_buffer_length(byte_length)
|
||||
return self.variant.max_utf8_buffer_length(byte_length);
|
||||
}
|
||||
DecoderLifeCycle::AtStart => {
|
||||
if let Some(utf8_bom) = checked_add(3, byte_length.checked_mul(3)) {
|
||||
@ -3745,7 +3743,7 @@ impl Decoder {
|
||||
| DecoderLifeCycle::AtUtf16BeStart => {
|
||||
return self
|
||||
.variant
|
||||
.max_utf8_buffer_length_without_replacement(byte_length)
|
||||
.max_utf8_buffer_length_without_replacement(byte_length);
|
||||
}
|
||||
DecoderLifeCycle::AtStart => {
|
||||
if let Some(utf8_bom) = byte_length.checked_add(3) {
|
||||
@ -4061,7 +4059,7 @@ impl Decoder {
|
||||
| DecoderLifeCycle::AtUtf8Start
|
||||
| DecoderLifeCycle::AtUtf16LeStart
|
||||
| DecoderLifeCycle::AtUtf16BeStart => {
|
||||
return self.variant.max_utf16_buffer_length(byte_length)
|
||||
return self.variant.max_utf16_buffer_length(byte_length);
|
||||
}
|
||||
DecoderLifeCycle::AtStart => {
|
||||
if let Some(utf8_bom) = byte_length.checked_add(1) {
|
||||
@ -5300,13 +5298,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_decode_bomful_invalid_utf8_to_cow_without_bom_handling_and_without_replacement() {
|
||||
assert!(
|
||||
UTF_8
|
||||
.decode_without_bom_handling_and_without_replacement(
|
||||
b"\xEF\xBB\xBF\xE2\x82\xAC\x80\xC3\xA4"
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(UTF_8
|
||||
.decode_without_bom_handling_and_without_replacement(
|
||||
b"\xEF\xBB\xBF\xE2\x82\xAC\x80\xC3\xA4"
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -5324,11 +5320,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_decode_invalid_windows_1257_to_cow_without_bom_handling_and_without_replacement() {
|
||||
assert!(
|
||||
WINDOWS_1257
|
||||
.decode_without_bom_handling_and_without_replacement(b"abc\x80\xA1\xE4")
|
||||
.is_none()
|
||||
);
|
||||
assert!(WINDOWS_1257
|
||||
.decode_without_bom_handling_and_without_replacement(b"abc\x80\xA1\xE4")
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
120
third_party/rust/encoding_rs/src/mem.rs
vendored
120
third_party/rust/encoding_rs/src/mem.rs
vendored
@ -29,7 +29,6 @@ use super::in_inclusive_range8;
|
||||
use super::in_range16;
|
||||
use super::in_range32;
|
||||
use super::DecoderResult;
|
||||
use super::EncoderResult;
|
||||
use ascii::*;
|
||||
use utf_8::*;
|
||||
|
||||
@ -37,10 +36,16 @@ macro_rules! non_fuzz_debug_assert {
|
||||
($($arg:tt)*) => (if !cfg!(fuzzing) { debug_assert!($($arg)*); })
|
||||
}
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "simd-accel")] {
|
||||
use ::std::intrinsics::likely;
|
||||
use ::std::intrinsics::unlikely;
|
||||
} else {
|
||||
#[inline(always)]
|
||||
// Unsafe to match the intrinsic, which is needlessly unsafe.
|
||||
unsafe fn likely(b: bool) -> bool {
|
||||
b
|
||||
}
|
||||
#[inline(always)]
|
||||
// Unsafe to match the intrinsic, which is needlessly unsafe.
|
||||
unsafe fn unlikely(b: bool) -> bool {
|
||||
@ -220,7 +225,7 @@ macro_rules! by_unit_check_simd {
|
||||
};
|
||||
}
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
|
||||
use simd_funcs::*;
|
||||
use simd::u8x16;
|
||||
@ -352,7 +357,7 @@ fn utf16_valid_up_to_alu(buffer: &[u16]) -> (usize, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
|
||||
#[inline(always)]
|
||||
fn is_str_latin1_impl(buffer: &str) -> Option<usize> {
|
||||
@ -441,7 +446,7 @@ fn is_utf8_latin1_impl(buffer: &[u8]) -> Option<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
|
||||
#[inline(always)]
|
||||
fn is_utf16_bidi_impl(buffer: &[u16]) -> bool {
|
||||
@ -491,7 +496,7 @@ cfg_if!{
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(all(feature = "simd-accel", any(target_feature = "sse2", all(target_endian = "little", target_arch = "aarch64"), all(target_endian = "little", target_feature = "neon"))))] {
|
||||
#[inline(always)]
|
||||
fn check_utf16_for_latin1_and_bidi_impl(buffer: &[u16]) -> Latin1Bidi {
|
||||
@ -687,10 +692,7 @@ pub fn is_utf16_latin1(buffer: &[u16]) -> bool {
|
||||
/// Returns `true` if the input is invalid UTF-8 or the input contains an
|
||||
/// RTL character. Returns `false` if the input is valid UTF-8 and contains
|
||||
/// no RTL characters.
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(collapsible_if, cyclomatic_complexity)
|
||||
)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(collapsible_if, cyclomatic_complexity))]
|
||||
#[inline]
|
||||
pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// As of rustc 1.25.0-nightly (73ac5d6a8 2018-01-11), this is faster
|
||||
@ -771,9 +773,11 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// Three-byte normal
|
||||
let second = unsafe { *(src.get_unchecked(read + 1)) };
|
||||
let third = unsafe { *(src.get_unchecked(read + 2)) };
|
||||
if ((UTF8_DATA.table[usize::from(second)] & unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
}) | (third >> 6))
|
||||
if ((UTF8_DATA.table[usize::from(second)]
|
||||
& unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
})
|
||||
| (third >> 6))
|
||||
!= 2
|
||||
{
|
||||
return true;
|
||||
@ -784,9 +788,11 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// Three-byte normal, potentially bidi
|
||||
let second = unsafe { *(src.get_unchecked(read + 1)) };
|
||||
let third = unsafe { *(src.get_unchecked(read + 2)) };
|
||||
if ((UTF8_DATA.table[usize::from(second)] & unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
}) | (third >> 6))
|
||||
if ((UTF8_DATA.table[usize::from(second)]
|
||||
& unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
})
|
||||
| (third >> 6))
|
||||
!= 2
|
||||
{
|
||||
return true;
|
||||
@ -806,9 +812,11 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// Three-byte normal, potentially bidi
|
||||
let second = unsafe { *(src.get_unchecked(read + 1)) };
|
||||
let third = unsafe { *(src.get_unchecked(read + 2)) };
|
||||
if ((UTF8_DATA.table[usize::from(second)] & unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
}) | (third >> 6))
|
||||
if ((UTF8_DATA.table[usize::from(second)]
|
||||
& unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
})
|
||||
| (third >> 6))
|
||||
!= 2
|
||||
{
|
||||
return true;
|
||||
@ -840,9 +848,11 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// Three-byte special lower bound, potentially bidi
|
||||
let second = unsafe { *(src.get_unchecked(read + 1)) };
|
||||
let third = unsafe { *(src.get_unchecked(read + 2)) };
|
||||
if ((UTF8_DATA.table[usize::from(second)] & unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
}) | (third >> 6))
|
||||
if ((UTF8_DATA.table[usize::from(second)]
|
||||
& unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
})
|
||||
| (third >> 6))
|
||||
!= 2
|
||||
{
|
||||
return true;
|
||||
@ -857,9 +867,11 @@ pub fn is_utf8_bidi(buffer: &[u8]) -> bool {
|
||||
// Three-byte special upper bound
|
||||
let second = unsafe { *(src.get_unchecked(read + 1)) };
|
||||
let third = unsafe { *(src.get_unchecked(read + 2)) };
|
||||
if ((UTF8_DATA.table[usize::from(second)] & unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
}) | (third >> 6))
|
||||
if ((UTF8_DATA.table[usize::from(second)]
|
||||
& unsafe {
|
||||
*(UTF8_DATA.table.get_unchecked(byte as usize + 0x80))
|
||||
})
|
||||
| (third >> 6))
|
||||
!= 2
|
||||
{
|
||||
return true;
|
||||
@ -1596,6 +1608,10 @@ pub fn convert_str_to_utf16(src: &str, dst: &mut [u16]) -> usize {
|
||||
///
|
||||
/// Returns the number of code units read and the number of bytes written.
|
||||
///
|
||||
/// Guarantees that the bytes in the destination beyond the number of
|
||||
/// bytes claimed as written by the second item of the return tuple
|
||||
/// are left unmodified.
|
||||
///
|
||||
/// Not all code units are read if there isn't enough output space.
|
||||
///
|
||||
/// Note that this method isn't designed for general streamability but for
|
||||
@ -1603,25 +1619,37 @@ pub fn convert_str_to_utf16(src: &str, dst: &mut [u16]) -> usize {
|
||||
/// if the input starts with or ends with an unpaired surrogate, those are
|
||||
/// replaced with the REPLACEMENT CHARACTER.
|
||||
///
|
||||
/// Matches the semantics of `TextEncoder.encodeInto()` from the
|
||||
/// Encoding Standard.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Note that this function may write garbage beyond the number of bytes
|
||||
/// indicated by the return value, so using a `&mut str` interpreted as
|
||||
/// `&mut [u8]` as the destination is not safe. If you want to convert into
|
||||
/// a `&mut str`, use `convert_utf16_to_str()` instead of this function.
|
||||
#[inline]
|
||||
/// If you want to convert into a `&mut str`, use
|
||||
/// `convert_utf16_to_str_partial()` instead of using this function
|
||||
/// together with the `unsafe` method `as_bytes_mut()` on `&mut str`.
|
||||
#[inline(always)]
|
||||
pub fn convert_utf16_to_utf8_partial(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
|
||||
let mut encoder = Utf8Encoder;
|
||||
let (result, read, written) = encoder.encode_from_utf16_raw(src, dst, true);
|
||||
debug_assert!(result == EncoderResult::OutputFull || read == src.len());
|
||||
(read, written)
|
||||
// The two functions called below are marked `inline(never)` to make
|
||||
// transitions from the hot part (first function) into the cold part
|
||||
// (second function) go through a return and another call to discouge
|
||||
// the CPU from speculating from the hot code into the cold code.
|
||||
// Letting the transitions be mere intra-function jumps, even to
|
||||
// basic blocks out-of-lined to the end of the function would wipe
|
||||
// away a quarter of Arabic encode performance on Haswell!
|
||||
let (read, written) = convert_utf16_to_utf16_partial_inner(src, dst);
|
||||
if unsafe { likely(read == src.len()) } {
|
||||
return (read, written);
|
||||
}
|
||||
let (tail_read, tail_written) =
|
||||
convert_utf16_to_utf16_partial_tail(&src[read..], &mut dst[written..]);
|
||||
(read + tail_read, written + tail_written)
|
||||
}
|
||||
|
||||
/// Converts potentially-invalid UTF-16 to valid UTF-8 with errors replaced
|
||||
/// with the REPLACEMENT CHARACTER.
|
||||
///
|
||||
/// The length of the destination buffer must be at least the length of the
|
||||
/// source buffer times three _plus one_.
|
||||
/// source buffer times three.
|
||||
///
|
||||
/// Returns the number of bytes written.
|
||||
///
|
||||
@ -1631,13 +1659,12 @@ pub fn convert_utf16_to_utf8_partial(src: &[u16], dst: &mut [u8]) -> (usize, usi
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Note that this function may write garbage beyond the number of bytes
|
||||
/// indicated by the return value, so using a `&mut str` interpreted as
|
||||
/// `&mut [u8]` as the destination is not safe. If you want to convert into
|
||||
/// a `&mut str`, use `convert_utf16_to_str()` instead of this function.
|
||||
#[inline]
|
||||
/// If you want to convert into a `&mut str`, use `convert_utf16_to_str()`
|
||||
/// instead of using this function together with the `unsafe` method
|
||||
/// `as_bytes_mut()` on `&mut str`.
|
||||
#[inline(always)]
|
||||
pub fn convert_utf16_to_utf8(src: &[u16], dst: &mut [u8]) -> usize {
|
||||
assert!(dst.len() > src.len() * 3);
|
||||
assert!(dst.len() >= src.len() * 3);
|
||||
let (read, written) = convert_utf16_to_utf8_partial(src, dst);
|
||||
debug_assert_eq!(read, src.len());
|
||||
written
|
||||
@ -1662,11 +1689,6 @@ pub fn convert_utf16_to_str_partial(src: &[u16], dst: &mut str) -> (usize, usize
|
||||
let (read, written) = convert_utf16_to_utf8_partial(src, bytes);
|
||||
let len = bytes.len();
|
||||
let mut trail = written;
|
||||
let max = ::std::cmp::min(len, trail + MAX_STRIDE_SIZE);
|
||||
while trail < max {
|
||||
bytes[trail] = 0;
|
||||
trail += 1;
|
||||
}
|
||||
while trail < len && ((bytes[trail] & 0xC0) == 0x80) {
|
||||
bytes[trail] = 0;
|
||||
trail += 1;
|
||||
@ -1679,16 +1701,16 @@ pub fn convert_utf16_to_str_partial(src: &[u16], dst: &mut str) -> (usize, usize
|
||||
/// signaled using the Rust type system.
|
||||
///
|
||||
/// The length of the destination buffer must be at least the length of the
|
||||
/// source buffer times three _plus one_.
|
||||
/// source buffer times three.
|
||||
///
|
||||
/// Returns the number of bytes written.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the destination buffer is shorter than stated above.
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
pub fn convert_utf16_to_str(src: &[u16], dst: &mut str) -> usize {
|
||||
assert!(dst.len() > src.len() * 3);
|
||||
assert!(dst.len() >= src.len() * 3);
|
||||
let (read, written) = convert_utf16_to_str_partial(src, dst);
|
||||
debug_assert_eq!(read, src.len());
|
||||
written
|
||||
|
807
third_party/rust/encoding_rs/src/utf_8.rs
vendored
807
third_party/rust/encoding_rs/src/utf_8.rs
vendored
@ -12,9 +12,10 @@ use ascii::ascii_to_basic_latin;
|
||||
use ascii::basic_latin_to_ascii;
|
||||
use ascii::validate_ascii;
|
||||
use handles::*;
|
||||
use mem::convert_utf16_to_utf8_partial;
|
||||
use variant::*;
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "simd-accel")] {
|
||||
use ::std::intrinsics::unlikely;
|
||||
use ::std::intrinsics::likely;
|
||||
@ -233,10 +234,7 @@ pub fn utf8_valid_up_to(src: &[u8]) -> usize {
|
||||
read
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(never_loop, cyclomatic_complexity)
|
||||
)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(never_loop, cyclomatic_complexity))]
|
||||
pub fn convert_utf8_to_utf16_up_to_invalid(src: &[u8], dst: &mut [u16]) -> (usize, usize) {
|
||||
// This algorithm differs from the UTF-8 validation algorithm, but making
|
||||
// this one consistent with that one makes this slower for reasons I don't
|
||||
@ -612,6 +610,223 @@ impl Utf8Decoder {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(never_loop))]
|
||||
#[inline(never)]
|
||||
pub fn convert_utf16_to_utf16_partial_inner(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
|
||||
let mut read = 0;
|
||||
let mut written = 0;
|
||||
'outer: loop {
|
||||
let mut unit = {
|
||||
let src_remaining = &src[read..];
|
||||
let dst_remaining = &mut dst[written..];
|
||||
let length = if dst_remaining.len() < src_remaining.len() {
|
||||
dst_remaining.len()
|
||||
} else {
|
||||
src_remaining.len()
|
||||
};
|
||||
match unsafe {
|
||||
basic_latin_to_ascii(src_remaining.as_ptr(), dst_remaining.as_mut_ptr(), length)
|
||||
} {
|
||||
None => {
|
||||
read += length;
|
||||
written += length;
|
||||
return (read, written);
|
||||
}
|
||||
Some((non_ascii, consumed)) => {
|
||||
read += consumed;
|
||||
written += consumed;
|
||||
non_ascii
|
||||
}
|
||||
}
|
||||
};
|
||||
'inner: loop {
|
||||
// The following loop is only broken out of as a goto forward.
|
||||
loop {
|
||||
// Unfortunately, this check isn't enough for the compiler to elide
|
||||
// the bound checks on writes to dst, which is why they are manually
|
||||
// elided, which makes a measurable difference.
|
||||
if written.checked_add(4).unwrap() > dst.len() {
|
||||
return (read, written);
|
||||
}
|
||||
read += 1;
|
||||
if unit < 0x800 {
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (unit >> 6) as u8 | 0xC0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
|
||||
if unsafe { likely(unit_minus_surrogate_start > (0xDFFF - 0xD800)) } {
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (unit >> 12) as u8 | 0xE0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = ((unit & 0xFC0) >> 6) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if unsafe { likely(unit_minus_surrogate_start <= (0xDBFF - 0xD800)) } {
|
||||
// high surrogate
|
||||
// read > src.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if read >= src.len() {
|
||||
debug_assert_eq!(read, src.len());
|
||||
// Unpaired surrogate at the end of the buffer.
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = 0xEFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBDu8;
|
||||
written += 1;
|
||||
}
|
||||
return (read, written);
|
||||
}
|
||||
let second = src[read];
|
||||
let second_minus_low_surrogate_start = second.wrapping_sub(0xDC00);
|
||||
if unsafe { likely(second_minus_low_surrogate_start <= (0xDFFF - 0xDC00)) } {
|
||||
// The next code unit is a low surrogate. Advance position.
|
||||
read += 1;
|
||||
let astral = (u32::from(unit) << 10) + u32::from(second)
|
||||
- (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (astral >> 18) as u8 | 0xF0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) =
|
||||
((astral & 0x3F000u32) >> 12) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) =
|
||||
((astral & 0xFC0u32) >> 6) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (astral & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// The next code unit is not a low surrogate. Don't advance
|
||||
// position and treat the high surrogate as unpaired.
|
||||
// Fall through
|
||||
}
|
||||
// Unpaired low surrogate
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = 0xEFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBDu8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Now see if the next unit is Basic Latin
|
||||
// read > src.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if read >= src.len() {
|
||||
debug_assert_eq!(read, src.len());
|
||||
return (read, written);
|
||||
}
|
||||
unit = src[read];
|
||||
if unsafe { unlikely(unit < 0x80) } {
|
||||
// written > dst.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if written >= dst.len() {
|
||||
debug_assert_eq!(written, dst.len());
|
||||
return (read, written);
|
||||
}
|
||||
dst[written] = unit as u8;
|
||||
read += 1;
|
||||
written += 1;
|
||||
// Mysteriously, adding a punctuation check here makes
|
||||
// the expected benificiary cases *slower*!
|
||||
continue 'outer;
|
||||
}
|
||||
continue 'inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn convert_utf16_to_utf16_partial_tail(src: &[u16], dst: &mut [u8]) -> (usize, usize) {
|
||||
// Everything below is cold code!
|
||||
let mut read = 0;
|
||||
let mut written = 0;
|
||||
let mut unit = src[read];
|
||||
// We now have up to 3 output slots, so an astral character
|
||||
// will not fit.
|
||||
if unit < 0x800 {
|
||||
loop {
|
||||
if unit < 0x80 {
|
||||
if written >= dst.len() {
|
||||
return (read, written);
|
||||
}
|
||||
read += 1;
|
||||
dst[written] = unit as u8;
|
||||
written += 1;
|
||||
} else if unit < 0x800 {
|
||||
if written + 2 > dst.len() {
|
||||
return (read, written);
|
||||
}
|
||||
read += 1;
|
||||
dst[written] = (unit >> 6) as u8 | 0xC0u8;
|
||||
written += 1;
|
||||
dst[written] = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
} else {
|
||||
return (read, written);
|
||||
}
|
||||
// read > src.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if read >= src.len() {
|
||||
debug_assert_eq!(read, src.len());
|
||||
return (read, written);
|
||||
}
|
||||
unit = src[read];
|
||||
}
|
||||
}
|
||||
// Could be an unpaired surrogate, but we'll need 3 output
|
||||
// slots in any case.
|
||||
if written + 3 > dst.len() {
|
||||
return (read, written);
|
||||
}
|
||||
read += 1;
|
||||
let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
|
||||
if unit_minus_surrogate_start <= (0xDFFF - 0xD800) {
|
||||
// Got surrogate
|
||||
if unit_minus_surrogate_start <= (0xDBFF - 0xD800) {
|
||||
// Got high surrogate
|
||||
if read >= src.len() {
|
||||
// Unpaired high surrogate
|
||||
unit = 0xFFFD;
|
||||
} else {
|
||||
let second = src[read];
|
||||
if in_inclusive_range16(second, 0xDC00, 0xDFFF) {
|
||||
// Valid surrogate pair, but we know it won't fit.
|
||||
read -= 1;
|
||||
return (read, written);
|
||||
}
|
||||
// Unpaired high
|
||||
unit = 0xFFFD;
|
||||
}
|
||||
} else {
|
||||
// Unpaired low
|
||||
unit = 0xFFFD;
|
||||
}
|
||||
}
|
||||
dst[written] = (unit >> 12) as u8 | 0xE0u8;
|
||||
written += 1;
|
||||
dst[written] = ((unit & 0xFC0) >> 6) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
dst[written] = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
debug_assert_eq!(written, dst.len());
|
||||
(read, written)
|
||||
}
|
||||
|
||||
pub struct Utf8Encoder;
|
||||
|
||||
impl Utf8Encoder {
|
||||
@ -623,7 +838,7 @@ impl Utf8Encoder {
|
||||
&self,
|
||||
u16_length: usize,
|
||||
) -> Option<usize> {
|
||||
checked_add(1, u16_length.checked_mul(3))
|
||||
u16_length.checked_mul(3)
|
||||
}
|
||||
|
||||
pub fn max_buffer_length_from_utf8_without_replacement(
|
||||
@ -633,150 +848,22 @@ impl Utf8Encoder {
|
||||
Some(byte_length)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(never_loop))]
|
||||
pub fn encode_from_utf16_raw(
|
||||
&mut self,
|
||||
src: &[u16],
|
||||
dst: &mut [u8],
|
||||
_last: bool,
|
||||
) -> (EncoderResult, usize, usize) {
|
||||
let mut read = 0;
|
||||
let mut written = 0;
|
||||
'outer: loop {
|
||||
let mut unit = {
|
||||
let src_remaining = &src[read..];
|
||||
let dst_remaining = &mut dst[written..];
|
||||
let (pending, length) = if dst_remaining.len() < src_remaining.len() {
|
||||
(EncoderResult::OutputFull, dst_remaining.len())
|
||||
} else {
|
||||
(EncoderResult::InputEmpty, src_remaining.len())
|
||||
};
|
||||
match unsafe {
|
||||
basic_latin_to_ascii(src_remaining.as_ptr(), dst_remaining.as_mut_ptr(), length)
|
||||
} {
|
||||
None => {
|
||||
read += length;
|
||||
written += length;
|
||||
return (pending, read, written);
|
||||
}
|
||||
Some((non_ascii, consumed)) => {
|
||||
read += consumed;
|
||||
written += consumed;
|
||||
non_ascii
|
||||
}
|
||||
}
|
||||
};
|
||||
'inner: loop {
|
||||
// The following loop is only broken out of as a goto forward.
|
||||
loop {
|
||||
// Unfortunately, this check isn't enough for the compiler to elide
|
||||
// the bound checks on writes to dst, which is why they are manually
|
||||
// elided, which makes a measurable difference.
|
||||
if written.checked_add(4).unwrap() > dst.len() {
|
||||
return (EncoderResult::OutputFull, read, written);
|
||||
}
|
||||
read += 1;
|
||||
if unit < 0x800 {
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (unit >> 6) as u8 | 0xC0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
let unit_minus_surrogate_start = unit.wrapping_sub(0xD800);
|
||||
if unsafe { likely(unit_minus_surrogate_start > (0xDFFF - 0xD800)) } {
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (unit >> 12) as u8 | 0xE0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) =
|
||||
((unit & 0xFC0) >> 6) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (unit & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if unsafe { likely(unit_minus_surrogate_start <= (0xDBFF - 0xD800)) } {
|
||||
// high surrogate
|
||||
// read > src.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if read >= src.len() {
|
||||
debug_assert_eq!(read, src.len());
|
||||
// Unpaired surrogate at the end of the buffer.
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = 0xEFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBDu8;
|
||||
written += 1;
|
||||
}
|
||||
return (EncoderResult::InputEmpty, read, written);
|
||||
}
|
||||
let second = src[read];
|
||||
let second_minus_low_surrogate_start = second.wrapping_sub(0xDC00);
|
||||
if unsafe { likely(second_minus_low_surrogate_start <= (0xDFFF - 0xDC00)) }
|
||||
{
|
||||
// The next code unit is a low surrogate. Advance position.
|
||||
read += 1;
|
||||
let astral = (u32::from(unit) << 10) + u32::from(second)
|
||||
- (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = (astral >> 18) as u8 | 0xF0u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) =
|
||||
((astral & 0x3F000u32) >> 12) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) =
|
||||
((astral & 0xFC0u32) >> 6) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = (astral & 0x3F) as u8 | 0x80u8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// The next code unit is not a low surrogate. Don't advance
|
||||
// position and treat the high surrogate as unpaired.
|
||||
// Fall through
|
||||
}
|
||||
// Unpaired low surrogate
|
||||
unsafe {
|
||||
*(dst.get_unchecked_mut(written)) = 0xEFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBFu8;
|
||||
written += 1;
|
||||
*(dst.get_unchecked_mut(written)) = 0xBDu8;
|
||||
written += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Now see if the next unit is Basic Latin
|
||||
// read > src.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if read >= src.len() {
|
||||
debug_assert_eq!(read, src.len());
|
||||
return (EncoderResult::InputEmpty, read, written);
|
||||
}
|
||||
unit = src[read];
|
||||
if unsafe { unlikely(unit < 0x80) } {
|
||||
// written > dst.len() is impossible, but using
|
||||
// >= instead of == allows the compiler to elide a bound check.
|
||||
if written >= dst.len() {
|
||||
debug_assert_eq!(written, dst.len());
|
||||
return (EncoderResult::OutputFull, read, written);
|
||||
}
|
||||
dst[written] = unit as u8;
|
||||
read += 1;
|
||||
written += 1;
|
||||
// Mysteriously, adding a punctuation check here makes
|
||||
// the expected benificiary cases *slower*!
|
||||
continue 'outer;
|
||||
}
|
||||
continue 'inner;
|
||||
}
|
||||
}
|
||||
let (read, written) = convert_utf16_to_utf8_partial(src, dst);
|
||||
(
|
||||
if read == src.len() {
|
||||
EncoderResult::InputEmpty
|
||||
} else {
|
||||
EncoderResult::OutputFull
|
||||
},
|
||||
read,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn encode_from_utf8_raw(
|
||||
@ -829,6 +916,39 @@ mod tests {
|
||||
encode_from_utf8(UTF_8, string, expect);
|
||||
}
|
||||
|
||||
fn encode_utf8_from_utf16_with_output_limit(
|
||||
string: &[u16],
|
||||
expect: &str,
|
||||
limit: usize,
|
||||
expect_result: EncoderResult,
|
||||
) {
|
||||
let mut dst = Vec::new();
|
||||
{
|
||||
dst.resize(limit, 0u8);
|
||||
let mut encoder = UTF_8.new_encoder();
|
||||
let (result, read, written) =
|
||||
encoder.encode_from_utf16_without_replacement(string, &mut dst, false);
|
||||
assert_eq!(result, expect_result);
|
||||
if expect_result == EncoderResult::InputEmpty {
|
||||
assert_eq!(read, string.len());
|
||||
}
|
||||
assert_eq!(&dst[..written], expect.as_bytes());
|
||||
}
|
||||
{
|
||||
dst.resize(64, 0u8);
|
||||
for (i, elem) in dst.iter_mut().enumerate() {
|
||||
*elem = i as u8;
|
||||
}
|
||||
let mut encoder = UTF_8.new_encoder();
|
||||
let (_, _, mut j) =
|
||||
encoder.encode_from_utf16_without_replacement(string, &mut dst, false);
|
||||
while j < dst.len() {
|
||||
assert_eq!(usize::from(dst[j]), j);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf8_decode() {
|
||||
// Empty
|
||||
@ -1013,6 +1133,404 @@ mod tests {
|
||||
encode_utf8_from_utf16(&[0xDC00, 0xDEDE], "\u{FFFD}\u{FFFD}".as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_utf8_from_utf16_with_output_limit() {
|
||||
encode_utf8_from_utf16_with_output_limit(&[0x0062], "\u{62}", 1, EncoderResult::InputEmpty);
|
||||
encode_utf8_from_utf16_with_output_limit(&[0x00A7], "\u{A7}", 2, EncoderResult::InputEmpty);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x2603],
|
||||
"\u{2603}",
|
||||
3,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDCA9],
|
||||
"\u{1F4A9}",
|
||||
4,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(&[0x00A7], "", 1, EncoderResult::OutputFull);
|
||||
encode_utf8_from_utf16_with_output_limit(&[0x2603], "", 2, EncoderResult::OutputFull);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDCA9],
|
||||
"",
|
||||
3,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x0062],
|
||||
"\u{63}\u{62}",
|
||||
2,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00A7],
|
||||
"\u{63}\u{A7}",
|
||||
3,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x2603],
|
||||
"\u{63}\u{2603}",
|
||||
4,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0xD83D, 0xDCA9],
|
||||
"\u{63}\u{1F4A9}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00A7],
|
||||
"\u{63}",
|
||||
2,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x2603],
|
||||
"\u{63}",
|
||||
3,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0xD83D, 0xDCA9],
|
||||
"\u{63}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0x0062],
|
||||
"\u{B6}\u{62}",
|
||||
3,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0x00A7],
|
||||
"\u{B6}\u{A7}",
|
||||
4,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0x2603],
|
||||
"\u{B6}\u{2603}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0xD83D, 0xDCA9],
|
||||
"\u{B6}\u{1F4A9}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0x00A7],
|
||||
"\u{B6}",
|
||||
3,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0x2603],
|
||||
"\u{B6}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x00B6, 0xD83D, 0xDCA9],
|
||||
"\u{B6}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062],
|
||||
"\u{263A}\u{62}",
|
||||
4,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x00A7],
|
||||
"\u{263A}\u{A7}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x2603],
|
||||
"\u{263A}\u{2603}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xD83D, 0xDCA9],
|
||||
"\u{263A}\u{1F4A9}",
|
||||
7,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x00A7],
|
||||
"\u{263A}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x2603],
|
||||
"\u{263A}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xD83D, 0xDCA9],
|
||||
"\u{263A}",
|
||||
6,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0x0062],
|
||||
"\u{1F60E}\u{62}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0x00A7],
|
||||
"\u{1F60E}\u{A7}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0x2603],
|
||||
"\u{1F60E}\u{2603}",
|
||||
7,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0xD83D, 0xDCA9],
|
||||
"\u{1F60E}\u{1F4A9}",
|
||||
8,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0x00A7],
|
||||
"\u{1F60E}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0x2603],
|
||||
"\u{1F60E}",
|
||||
6,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0xD83D, 0xDE0E, 0xD83D, 0xDCA9],
|
||||
"\u{1F60E}",
|
||||
7,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x0062],
|
||||
"\u{63}\u{B6}\u{62}\u{62}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x0062],
|
||||
"\u{63}\u{B6}\u{62}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x0062, 0x0062],
|
||||
"\u{63}\u{B6}\u{62}\u{62}\u{62}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x0062, 0x0062],
|
||||
"\u{63}\u{B6}\u{62}\u{62}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x0062],
|
||||
"\u{263A}\u{62}\u{62}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x0062],
|
||||
"\u{263A}\u{62}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x0062, 0x0062],
|
||||
"\u{263A}\u{62}\u{62}\u{62}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x0062, 0x0062],
|
||||
"\u{263A}\u{62}\u{62}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x00A7],
|
||||
"\u{63}\u{B6}\u{A7}",
|
||||
5,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x00A7],
|
||||
"\u{63}\u{B6}",
|
||||
4,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x00A7, 0x0062],
|
||||
"\u{63}\u{B6}\u{A7}\u{62}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x00A7, 0x0062],
|
||||
"\u{63}\u{B6}\u{A7}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x00A7, 0x0062],
|
||||
"\u{263A}\u{A7}\u{62}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x00A7, 0x0062],
|
||||
"\u{263A}\u{A7}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x00A7],
|
||||
"\u{63}\u{B6}\u{62}\u{A7}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x0062, 0x00A7],
|
||||
"\u{63}\u{B6}\u{62}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x00A7],
|
||||
"\u{263A}\u{62}\u{A7}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x0062, 0x00A7],
|
||||
"\u{263A}\u{62}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x2603],
|
||||
"\u{63}\u{B6}\u{2603}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0x2603],
|
||||
"\u{63}\u{B6}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x2603],
|
||||
"\u{263A}\u{2603}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0x2603],
|
||||
"\u{263A}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0xD83D],
|
||||
"\u{63}\u{B6}\u{FFFD}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0xD83D],
|
||||
"\u{63}\u{B6}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xD83D],
|
||||
"\u{263A}\u{FFFD}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xD83D],
|
||||
"\u{263A}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0xDCA9],
|
||||
"\u{63}\u{B6}\u{FFFD}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x0063, 0x00B6, 0xDCA9],
|
||||
"\u{63}\u{B6}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xDCA9],
|
||||
"\u{263A}\u{FFFD}",
|
||||
6,
|
||||
EncoderResult::InputEmpty,
|
||||
);
|
||||
encode_utf8_from_utf16_with_output_limit(
|
||||
&[0x263A, 0xDCA9],
|
||||
"\u{263A}",
|
||||
5,
|
||||
EncoderResult::OutputFull,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf8_max_length_from_utf16() {
|
||||
let mut encoder = UTF_8.new_encoder();
|
||||
@ -1114,7 +1632,6 @@ mod tests {
|
||||
assert!(!had_errors);
|
||||
assert_eq!(output[0], 0x00E4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use super::*;
|
||||
use handles::*;
|
||||
use variant::*;
|
||||
|
||||
cfg_if!{
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "simd-accel")] {
|
||||
use simd_funcs::*;
|
||||
use simd::u16x8;
|
||||
|
@ -197,11 +197,14 @@ const char nsXPLookAndFeel::sColorPrefs[][41] = {
|
||||
"ui.-moz-mac-source-list-selection",
|
||||
"ui.-moz-mac-active-source-list-selection",
|
||||
"ui.-moz-mac-tooltip",
|
||||
"ui.-moz-win-accentcolor",
|
||||
"ui.-moz-win-accentcolortext",
|
||||
"ui.-moz-win-mediatext",
|
||||
"ui.-moz-win-communicationstext",
|
||||
"ui.-moz-nativehyperlinktext",
|
||||
"ui.-moz-comboboxtext",
|
||||
"ui.-moz-combobox"};
|
||||
"ui.-moz-combobox",
|
||||
"ui.-moz-gtk-info-bar-text"};
|
||||
|
||||
int32_t nsXPLookAndFeel::sCachedColors[LookAndFeel::eColorID_LAST_COLOR] = {0};
|
||||
int32_t nsXPLookAndFeel::sCachedColorBits[COLOR_CACHE_SIZE] = {0};
|
||||
|
@ -19,8 +19,8 @@ use conversions::encoding_rs::Encoding;
|
||||
/// Required math stated in the docs of
|
||||
/// `convert_utf16_to_utf8()`.
|
||||
#[inline(always)]
|
||||
fn times_three_plus_one(a: usize) -> Option<usize> {
|
||||
a.checked_mul(3)?.checked_add(1)
|
||||
fn times_three(a: usize) -> Option<usize> {
|
||||
a.checked_mul(3)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -321,7 +321,7 @@ impl nsACString {
|
||||
// point. BUT if the worst case fits inside the inline capacity of an autostring, we skip
|
||||
// the ASCII stuff.
|
||||
let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() {
|
||||
let worst_case = times_three_plus_one(other.len()).ok_or(())?;
|
||||
let worst_case = times_three(other.len()).ok_or(())?;
|
||||
if worst_case <= inline_capacity {
|
||||
Some(worst_case)
|
||||
} else {
|
||||
@ -340,7 +340,7 @@ impl nsACString {
|
||||
return Ok(handle.finish(old_len + written, true));
|
||||
}
|
||||
let filled = old_len + written;
|
||||
let needed = times_three_plus_one(left).ok_or(())?;
|
||||
let needed = times_three(left).ok_or(())?;
|
||||
let new_len = filled.checked_add(needed).ok_or(())?;
|
||||
unsafe {
|
||||
handle.restart_bulk_write(new_len, filled, false)?;
|
||||
@ -351,7 +351,7 @@ impl nsACString {
|
||||
let needed = if let Some(n) = worst_case_needed {
|
||||
n
|
||||
} else {
|
||||
times_three_plus_one(other.len()).ok_or(())?
|
||||
times_three(other.len()).ok_or(())?
|
||||
};
|
||||
let new_len = old_len.checked_add(needed).ok_or(())?;
|
||||
let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? };
|
||||
|
@ -51,13 +51,13 @@ char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) {
|
||||
// The uses of this function seem temporary enough that it's not
|
||||
// worthwhile to be fancy about the allocation size. Let's just use
|
||||
// the worst case.
|
||||
// Times 3 plus 2, because ConvertUTF16toUTF8 requires times 3 plus 1 and
|
||||
// Times 3 plus 1, because ConvertUTF16toUTF8 requires times 3 and
|
||||
// then we have the terminator.
|
||||
// Using CheckedInt<uint32_t>, because aUTF8Count is uint32_t* for
|
||||
// historical reasons.
|
||||
mozilla::CheckedInt<uint32_t> destLen(len);
|
||||
destLen *= 3;
|
||||
destLen += 2;
|
||||
destLen += 1;
|
||||
if (!destLen.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -129,8 +129,7 @@ inline void ConvertLatin1toUTF16(mozilla::Span<const char> aSource,
|
||||
/**
|
||||
* Lone surrogates are replaced with the REPLACEMENT CHARACTER.
|
||||
*
|
||||
* The length of aDest must be at least the length of aSource times three
|
||||
* _plus one_.
|
||||
* The length of aDest must be at least the length of aSource times three.
|
||||
*
|
||||
* Returns the number of code units written.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user