Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-09-07 17:21:27 +02:00
commit 656d6db419
96 changed files with 1834 additions and 760 deletions

3
.gitignore vendored
View File

@ -119,3 +119,6 @@ testing/talos/talos/tests/devtools/damp.manifest.develop
# Ignore files created when running a reftest.
lextab.py
# tup database
/.tup

View File

@ -129,3 +129,6 @@ GPATH
# Ignore files created when running a reftest.
^lextab.py$
# tup database
^\.tup

View File

@ -171,6 +171,10 @@ faster: install-dist/idl
$(MAKE) -C faster FASTER_RECURSIVE_MAKE=1
endif
.PHONY: tup
tup: install-manifests buildid.h
@$(TUP)
# process_install_manifest needs to be invoked with --no-remove when building
# js as standalone because automated builds are building nspr separately and
# that would remove the resulting files.

View File

@ -24,11 +24,12 @@
"size" : 12072532
},
{
"size" : 89319524,
"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
"algorithm" : "sha512",
"filename" : "rustc.tar.xz",
"unpack" : true
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 131489924,
"digest": "59f7463a0da38f324daa4ffc2678d78afb4fe0df13248c1d215bcb996ec05e8521155563cde9a8b719a9b98c5feeaf97cc9e8d52c9b95f6b44728870d908d5b6",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"algorithm" : "sha512",

View File

@ -16,9 +16,9 @@
"unpack": true
},
{
"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 102276708,
"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 131489924,
"digest": "59f7463a0da38f324daa4ffc2678d78afb4fe0df13248c1d215bcb996ec05e8521155563cde9a8b719a9b98c5feeaf97cc9e8d52c9b95f6b44728870d908d5b6",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true

View File

@ -16,9 +16,9 @@
"unpack": true
},
{
"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 102276708,
"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 131489924,
"digest": "59f7463a0da38f324daa4ffc2678d78afb4fe0df13248c1d215bcb996ec05e8521155563cde9a8b719a9b98c5feeaf97cc9e8d52c9b95f6b44728870d908d5b6",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true

View File

@ -24,11 +24,12 @@
"size" : 12072532
},
{
"unpack" : true,
"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
"filename" : "rustc.tar.xz",
"algorithm" : "sha512",
"size" : 89319524
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 131489924,
"digest": "59f7463a0da38f324daa4ffc2678d78afb4fe0df13248c1d215bcb996ec05e8521155563cde9a8b719a9b98c5feeaf97cc9e8d52c9b95f6b44728870d908d5b6",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"filename" : "sccache.tar.bz2",

View File

@ -16,9 +16,9 @@
"unpack": true
},
{
"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 102276708,
"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 131489924,
"digest": "59f7463a0da38f324daa4ffc2678d78afb4fe0df13248c1d215bcb996ec05e8521155563cde9a8b719a9b98c5feeaf97cc9e8d52c9b95f6b44728870d908d5b6",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true

View File

@ -55,19 +55,11 @@
"filename": "genisoimage.tar.xz"
},
{
"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 102276708,
"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 171059204,
"digest": "7554ac993f55818827c80dab90135209e57db70c7c9131bef4309aff3b8d7452c4c0de663df7e8c46bd5702455c36292ade6c7a8007e567c4588c7f91aa88b57",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"size": 53754194,
"visibility": "public",
"digest": "d861a8c857e5cbc98318951b009c82cd88cbf151f1f5ad83d140e5e662b92c1ae1250c71c5d4cd29ebbcb4efa360a2ed4a6eb4db6281ecfb313038b5487eb1d2",
"algorithm": "sha512",
"filename": "rust-std-lib-x86_64-apple-darwin.tar.bz2",
"unpack": true
}
]

View File

@ -8,9 +8,9 @@
"unpack": true
},
{
"version": "rust 1.10 repack",
"size": 150726627,
"digest": "a30476113212895a837b2c4c18eb34d767c7192c3e327fba84c0138eaf7e671e84d5294e75370af3fe7e527a61e0938cd6cce20fba0aec94537070eb0094e27e",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 146060042,
"digest": "c7c5556af0dea1f97a737e4634496d407a5e0f7d14a7013746ad41ef188bab03be60cea59ed63d733dcb03bf11b05d8bf637dc0261f15cd5b0ab46d1199243cf",
"algorithm": "sha512",
"filename": "rustc.tar.bz2",
"unpack": true

View File

@ -6,10 +6,11 @@
"filename": "mozmake.exe"
},
{
"size": 78886322,
"digest": "9c2c40637de27a0852aa1166f2a08159908b23f7a55855c933087c541461bbb2a1ec9e0522df0d2b9da2b2c343b673dbb5a2fa8d30216fe8acee1eb1383336ea",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 86199150,
"digest": "fec209dc85a098817c892655fbfda2bd6961199b1c28422994a50daddcb219608673b87dde30b3380555400cf4484863a12d431a6a25ef01cb9b1b32bef48f8b",
"algorithm": "sha512",
"filename": "rustc-beta-i686-pc-windows-msvc.tar.bz2",
"filename": "rustc.tar.bz2",
"unpack": true
},
{

View File

@ -6,9 +6,9 @@
"filename": "mozmake.exe"
},
{
"version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 88820579,
"digest": "3bc772d951bf90b01cdba9dcd0e1d131a98519dff0710bb219784ea43d4d001dbce191071a4b3824933386bb9613f173760c438939eb396b0e0dfdad9a42e4f0",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 86199150,
"digest": "fec209dc85a098817c892655fbfda2bd6961199b1c28422994a50daddcb219608673b87dde30b3380555400cf4484863a12d431a6a25ef01cb9b1b32bef48f8b",
"algorithm": "sha512",
"filename": "rustc.tar.bz2",
"unpack": true

View File

@ -6,8 +6,9 @@
"filename": "mozmake.exe"
},
{
"size": 80157273,
"digest": "c4704dcc6774b9f3baaa9313192d26e36bfba2d4380d0518ee7cb89153d9adfe63f228f0ac29848f02948eb1ab7e6624ba71210f0121196d2b54ecebd640d1e6",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 91329933,
"digest": "db97f0186db432c57698e287798940abb5946c8903f990b087ea977fb938e83f2f9ca1bf90377bc575563af3144d429cc897a36750a1978a288a42b132c3d25d",
"algorithm": "sha512",
"visibility": "public",
"filename": "rustc.tar.bz2",

View File

@ -6,9 +6,9 @@
"filename": "mozmake.exe"
},
{
"version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 94067220,
"digest": "05cabda2a28ce6674f062aab589b4b3758e0cd4a4af364bb9a2e736254baa10d668936b2b7ed0df530c7f5ba8ea1e7f51ff3affc84a6551c46188b2f67f10e05",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 91329933,
"digest": "db97f0186db432c57698e287798940abb5946c8903f990b087ea977fb938e83f2f9ca1bf90377bc575563af3144d429cc897a36750a1978a288a42b132c3d25d",
"algorithm": "sha512",
"visibility": "public",
"filename": "rustc.tar.bz2",

View File

@ -323,6 +323,7 @@ case "$target" in
MOZ_ANDROID_AAR(design, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
MOZ_ANDROID_AAR(recyclerview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
MOZ_ANDROID_AAR(support-v4, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL)
MOZ_ANDROID_AAR(palette-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/$ANDROID_SUPPORT_LIBRARY_VERSION/support-annotations-$ANDROID_SUPPORT_LIBRARY_VERSION.jar"
AC_MSG_CHECKING([for support-annotations JAR])

View File

@ -8,6 +8,9 @@
</head>
<body>
<div id="mount"></div>
<script type="text/javascript">
var devtoolsRequire = Components.utils.import("resource://devtools/shared/Loader.jsm", {}).require;
</script>
<script type="text/javascript" src="resource://devtools/client/debugger/new/bundle.js"></script>
</body>
</html>

View File

@ -70,6 +70,7 @@ function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
// Bug 1225160 - Using source maps with browser debugging can lead to a crash
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
}
window.addEventListener("load", function() {

View File

@ -7,6 +7,11 @@
const HIGHLIGHTER_TYPE = "EyeDropper";
const ID = "eye-dropper-";
const TEST_URI = `
<style>
html{width:100%;height:100%;}
</style>
<body>eye-dropper test</body>`;
const MOVE_EVENTS_DATA = [
{type: "mouse", x: 200, y: 100, expected: {x: 200, y: 100}},
@ -19,26 +24,91 @@ const MOVE_EVENTS_DATA = [
{type: "keyboard", key: "VK_DOWN", shift: true, expected: {x: 100, y: 211}},
{type: "keyboard", key: "VK_UP", expected: {x: 100, y: 210}},
{type: "keyboard", key: "VK_UP", shift: true, expected: {x: 100, y: 200}},
// Mouse initialization for left and top snapping
{type: "mouse", x: 7, y: 7, expected: {x: 7, y: 7}},
// Left Snapping
{type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 0, y: 7},
desc: "Left Snapping to x=0"},
// Top Snapping
{type: "keyboard", key: "VK_UP", shift: true, expected: {x: 0, y: 0},
desc: "Top Snapping to y=0"},
// Mouse initialization for right snapping
{
type: "mouse",
x: (width, height) => width - 5,
y: 0,
expected: {
x: (width, height) => width - 5,
y: 0
}
},
// Right snapping
{
type: "keyboard",
key: "VK_RIGHT",
shift: true,
expected: {
x: (width, height) => width,
y: 0
},
desc: "Right snapping to x=max window width available"
},
// Mouse initialization for bottom snapping
{
type: "mouse",
x: 0,
y: (width, height) => height - 5,
expected: {
x: 0,
y: (width, height) => height - 5
}
},
// Bottom snapping
{
type: "keyboard",
key: "VK_DOWN",
shift: true,
expected: {
x: 0,
y: (width, height) => height
},
desc: "Bottom snapping to y=max window height available"
},
];
add_task(function* () {
let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
let {inspector, testActor} = yield openInspectorForURL(
"data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)({inspector, testActor});
helper.prefix = ID;
yield helper.show("html");
yield respondsToMoveEvents(helper);
yield respondsToMoveEvents(helper, testActor);
yield respondsToReturnAndEscape(helper);
helper.finalize();
});
function* respondsToMoveEvents(helper) {
function* respondsToMoveEvents(helper, testActor) {
info("Checking that the eyedropper responds to events from the mouse and keyboard");
let {mouse} = helper;
let {width, height} = yield testActor.getBoundingClientRect("html");
for (let {type, x, y, key, shift, expected, desc} of MOVE_EVENTS_DATA) {
x = typeof x === "function" ? x(width, height) : x;
y = typeof y === "function" ? y(width, height) : y;
expected.x = typeof expected.x === "function" ?
expected.x(width, height) : expected.x;
expected.y = typeof expected.y === "function" ?
expected.y(width, height) : expected.y;
if (typeof desc === "undefined") {
info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`);
} else {
info(`Simulating ${type} event: ${desc}`);
}
for (let {type, x, y, key, shift, expected} of MOVE_EVENTS_DATA) {
info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`);
if (type === "mouse") {
yield mouse.move(x, y);
} else if (type === "keyboard") {

View File

@ -325,6 +325,11 @@
display: inline-block;
}
.ruleview-expander.theme-twisty:-moz-locale-dir(rtl) {
/* for preventing .theme-twisty's wrong direction in rtl; Bug 1296648 */
transform: none;
}
.ruleview-newproperty {
/* (enable checkbox width: 12px) + (expander width: 15px) */
margin-inline-start: 27px;

View File

@ -111,7 +111,7 @@ var inputTests = [
input: '["' + testStrIn + '", "' + testStrIn + '", "' + testStrIn + '"]',
output: 'Array [ "' + testStrOut + '", "' + testStrOut + '", "' +
testStrOut + '" ]',
inspectable: false,
inspectable: true,
printOutput: "SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE " +
"ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. " +
"ESCAPE ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\n" +
@ -124,7 +124,8 @@ var inputTests = [
input: '({0: "a", 1: "b"})',
output: 'Object [ "a", "b" ]',
printOutput: "[object Object]",
inspectable: false,
inspectable: true,
variablesViewLabel: "Object[2]",
},
// 14
@ -132,7 +133,8 @@ var inputTests = [
input: '({0: "a", 42: "b"})',
output: 'Object { 0: "a", 42: "b" }',
printOutput: "[object Object]",
inspectable: false,
inspectable: true,
variablesViewLabel: "Object",
},
// 15
@ -165,6 +167,96 @@ var inputTests = [
inspectable: true,
variablesViewLabel: "Object",
},
// 18
{
input: '({})',
output: 'Object { }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 19
{
input: '({length: 0})',
output: 'Object [ ]',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object[0]",
},
// 20
{
input: '({length: 1})',
output: 'Object { length: 1 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 21
{
input: '({0: "a", 1: "b", length: 1})',
output: 'Object { 1: "b", length: 1, 1 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 22
{
input: '({0: "a", 1: "b", length: 2})',
output: 'Object [ "a", "b" ]',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object[2]",
},
// 23
{
input: '({0: "a", 1: "b", length: 3})',
output: 'Object { length: 3, 2 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 24
{
input: '({0: "a", 2: "b", length: 2})',
output: 'Object { 2: "b", length: 2, 1 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 25
{
input: '({0: "a", 2: "b", length: 3})',
output: 'Object { length: 3, 2 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 26
{
input: '({0: "a", b: "b", length: 1})',
output: 'Object { b: "b", length: 1, 1 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 27
{
input: '({0: "a", b: "b", length: 2})',
output: 'Object { b: "b", length: 2, 1 more\u2026 }',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
];
function test() {

View File

@ -429,8 +429,10 @@ EyeDropper.prototype = {
offsetX *= modifier;
if (offsetX !== 0 || offsetY !== 0) {
this.magnifiedArea.x += offsetX;
this.magnifiedArea.y += offsetY;
this.magnifiedArea.x = cap(this.magnifiedArea.x + offsetX,
0, this.win.innerWidth * this.pageZoom);
this.magnifiedArea.y = cap(this.magnifiedArea.y + offsetY, 0,
this.win.innerHeight * this.pageZoom);
this.draw();
@ -526,3 +528,7 @@ function hexString([r, g, b]) {
let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
return "#" + val.toString(16).substr(-6).toUpperCase();
}
function cap(value, min, max) {
return Math.max(min, Math.min(value, max));
}

View File

@ -1800,13 +1800,27 @@ DebuggerServer.ObjectActorPreviewers.Object = [
return false;
}
// Making sure that all keys are array indices, that is:
// `ToString(ToUint32(key)) === key && key !== "4294967295"`.
// Also ensuring that the keys are consecutive and start at "0",
// this implies checking `key !== "4294967295"` is not necessary.
// Pseudo-arrays should only have array indices and, optionally, a "length" property.
// Since array indices are sorted first, check if the last property is "length".
if(keys[keys.length-1] === "length") {
keys.pop();
// The value of "length" should equal the number of other properties. If eventually
// we allow sparse pseudo-arrays, we should check whether it's a Uint32 instead.
if(rawObj.length !== keys.length) {
return false;
}
}
// Ensure that the keys are consecutive integers starting at "0". If eventually we
// allow sparse pseudo-arrays, we should check that they are array indices, that is:
// `(key >>> 0) + '' === key && key !== "4294967295"`.
// Checking the last property first allows us to avoid useless iterations when
// there is any property which is not an array index.
if(keys.length && keys[keys.length-1] !== keys.length - 1 + '') {
return false;
}
for (let key of keys) {
let numKey = key >>> 0; // ToUint32(key)
if (numKey + '' != key || numKey != length++) {
if (key !== (length++) + '') {
return false;
}
}

View File

@ -8546,11 +8546,14 @@ nsDocShell::RestoreFromHistory()
int32_t minFontSize = 0;
float textZoom = 1.0f;
float pageZoom = 1.0f;
float overrideDPPX = 0.0f;
bool styleDisabled = false;
if (oldCv && newCv) {
oldCv->GetMinFontSize(&minFontSize);
oldCv->GetTextZoom(&textZoom);
oldCv->GetFullZoom(&pageZoom);
oldCv->GetOverrideDPPX(&overrideDPPX);
oldCv->GetAuthorStyleDisabled(&styleDisabled);
}
@ -8783,6 +8786,7 @@ nsDocShell::RestoreFromHistory()
newCv->SetMinFontSize(minFontSize);
newCv->SetTextZoom(textZoom);
newCv->SetFullZoom(pageZoom);
newCv->SetOverrideDPPX(overrideDPPX);
newCv->SetAuthorStyleDisabled(styleDisabled);
}
@ -9276,6 +9280,7 @@ nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer)
int32_t minFontSize;
float textZoom;
float pageZoom;
float overrideDPPX;
bool styleDisabled;
// |newMUDV| also serves as a flag to set the data from the above vars
nsCOMPtr<nsIContentViewer> newCv;
@ -9317,6 +9322,8 @@ nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer)
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(oldCv->GetFullZoom(&pageZoom),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(oldCv->GetAuthorStyleDisabled(&styleDisabled),
NS_ERROR_FAILURE);
}
@ -9385,6 +9392,8 @@ nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer)
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newCv->SetFullZoom(pageZoom),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newCv->SetAuthorStyleDisabled(styleDisabled),
NS_ERROR_FAILURE);
}

View File

@ -8,6 +8,7 @@
// non-native version will be less than optimal.
#include "MurmurHash3.h"
#include <stdlib.h>
namespace {
@ -20,8 +21,6 @@ namespace {
#define FORCE_INLINE __forceinline
#include <stdlib.h>
#define ROTL32(x,y) _rotl(x,y)
#define ROTL64(x,y) _rotl64(x,y)

View File

@ -1810,7 +1810,11 @@ ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
uint32_t offset = aEvent->mInput.mOffset;
const uint32_t kEndOffset = offset + aEvent->mInput.mLength;
bool wasLineBreaker = false;
// lastCharRect stores the last charRect value (see below for the detail of
// charRect).
nsRect lastCharRect;
// lastFrame is base frame of lastCharRect.
nsIFrame* lastFrame;
while (offset < kEndOffset) {
nsCOMPtr<nsIContent> lastTextContent;
rv = SetRangeFromFlatTextOffset(range, offset, 1, lineBreakType, true,
@ -1853,18 +1857,14 @@ ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
return NS_ERROR_FAILURE;
}
// get the starting frame rect
nsRect frameRect(nsPoint(0, 0), firstFrame->GetRect().Size());
rv = ConvertToRootRelativeOffset(firstFrame, frameRect);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool startsBetweenLineBreaker = false;
nsAutoString chars;
// XXX not bidi-aware this class...
isVertical = firstFrame->GetWritingMode().IsVertical();
nsIFrame* baseFrame = firstFrame;
// charRect should have each character rect or line breaker rect relative
// to the base frame.
AutoTArray<nsRect, 16> charRects;
// If the first frame is a text frame, the result should be computed with
@ -1937,10 +1937,8 @@ ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
// is better than guessing the rect from the previous character.)
if (firstFrame->GetType() != nsGkAtoms::brFrame &&
aEvent->mInput.mOffset != offset) {
// The frame position in the root widget will be added in the
// following for loop but we need the rect in the previous frame.
// So, we need to avoid using current frame position.
brRect = lastCharRect - frameRect.TopLeft();
baseFrame = lastFrame;
brRect = lastCharRect;
if (!wasLineBreaker) {
if (isVertical) {
// Right of the last character.
@ -1962,7 +1960,16 @@ ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
if (NS_WARN_IF(!brRectRelativeToLastTextFrame.IsValid())) {
return NS_ERROR_FAILURE;
}
brRect = brRectRelativeToLastTextFrame.RectRelativeTo(firstFrame);
// Look for the last text frame for lastTextContent.
nsIFrame* primaryFrame = lastTextContent->GetPrimaryFrame();
if (NS_WARN_IF(!primaryFrame)) {
return NS_ERROR_FAILURE;
}
baseFrame = primaryFrame->LastContinuation();
if (NS_WARN_IF(!baseFrame)) {
return NS_ERROR_FAILURE;
}
brRect = brRectRelativeToLastTextFrame.RectRelativeTo(baseFrame);
}
// Otherwise, we need to compute the line breaker's rect only with the
// first frame's rect. But this may be unexpected. For example,
@ -2001,9 +2008,17 @@ ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
for (size_t i = 0; i < charRects.Length() && offset < kEndOffset; i++) {
nsRect charRect = charRects[i];
charRect.x += frameRect.x;
charRect.y += frameRect.y;
// Store lastCharRect before applying CSS transform because it may be
// used for computing a line breaker rect. Then, the computed line
// breaker rect will be applied CSS transform again. Therefore,
// the value of lastCharRect should be raw rect value relative to the
// base frame.
lastCharRect = charRect;
lastFrame = baseFrame;
rv = ConvertToRootRelativeOffset(baseFrame, charRect);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rect = LayoutDeviceIntRect::FromUnknownRect(
charRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));

View File

@ -618,9 +618,19 @@ TabParent::ActorDestroy(ActorDestroyReason why)
if (why == AbnormalShutdown && os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
"oop-frameloader-crashed", nullptr);
nsContentUtils::DispatchTrustedEvent(frameElement->OwnerDoc(), frameElement,
NS_LITERAL_STRING("oop-browser-crashed"),
true, true);
nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(frameElement);
if (owner) {
RefPtr<nsFrameLoader> currentFrameLoader = owner->GetFrameLoader();
// It's possible that the frameloader owner has already moved on
// and created a new frameloader. If so, we don't fire the event,
// since the frameloader owner has clearly moved on.
if (currentFrameLoader == frameLoader) {
nsContentUtils::DispatchTrustedEvent(frameElement->OwnerDoc(), frameElement,
NS_LITERAL_STRING("oop-browser-crashed"),
true, true);
}
}
}
mFrameLoader = nullptr;

View File

@ -13,8 +13,12 @@ from marionette.marionette_test import SkipTest
from firefox_puppeteer.testcases import BaseFirefoxTestCase
from external_media_tests.utils import (timestamp_now, verbose_until)
from external_media_tests.media_utils.video_puppeteer import (playback_done, playback_started,
VideoException, VideoPuppeteer as VP)
from external_media_tests.media_utils.video_puppeteer import (
playback_done,
playback_started,
VideoException,
VideoPuppeteer as VP
)
class MediaTestCase(BaseFirefoxTestCase, MarionetteTestCase):
@ -47,7 +51,8 @@ class MediaTestCase(BaseFirefoxTestCase, MarionetteTestCase):
img_data = self.marionette.screenshot()
with open(path, 'wb') as f:
f.write(img_data.decode('base64'))
self.marionette.log('Screenshot saved in %s' % os.path.abspath(path))
self.marionette.log('Screenshot saved in {}'
.format(os.path.abspath(path)))
def log_video_debug_lines(self):
"""
@ -160,11 +165,6 @@ class VideoPlaybackTestsMixin(object):
# is not 0
self.check_playback_starts(video)
video.pause()
src = video.video_src
if not src.startswith('mediasource'):
self.marionette.log('video is not '
'mediasource: %s' % src,
level='WARNING')
except TimeoutException as e:
raise self.failureException(e)
@ -268,11 +268,12 @@ class EMESetupMixin(object):
script_timeout=60000)
if not adobe_result == 'success':
raise VideoException(
'ERROR: Resetting Adobe GMP failed % s' % adobe_result)
'ERROR: Resetting Adobe GMP failed {}'
.format(adobe_result))
if not widevine_result == 'success':
raise VideoException(
'ERROR: Resetting Widevine GMP failed % s'
% widevine_result)
'ERROR: Resetting Widevine GMP failed {}'
.format(widevine_result))
EMESetupMixin.version_needs_reset = False
@ -281,13 +282,13 @@ class EMESetupMixin(object):
pref_value = self.prefs.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref %s has no value.' % pref_name)
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref %s = %s' % (pref_name, pref_value))
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
if pref_value != expected_value:
self.logger.info('Pref %s has unexpected value.'
% pref_name)
self.logger.info('Pref {} has unexpected value.'
.format(pref_name))
return False
return True
@ -297,14 +298,15 @@ class EMESetupMixin(object):
pref_value = self.prefs.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref %s has no value.' % pref_name)
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref %s = %s' % (pref_name, pref_value))
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
match = re.search('^\d+$', pref_value)
if not match:
self.logger.info('Pref %s is not an integer' % pref_name)
self.logger.info('Pref {} is not an integer'
.format(pref_name))
return False
return pref_value >= minimum_value
@ -323,15 +325,15 @@ class EMESetupMixin(object):
pref_value = self.prefs.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref %s has no value.' % pref_name)
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref %s = %s' % (pref_name, pref_value))
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
match = re.search('^\d(.\d+)*$', pref_value)
if not match:
self.logger.info('Pref %s is not a version string'
% pref_name)
self.logger.info('Pref {} is not a version string'
.format(pref_name))
return False
pref_ints = [int(n) for n in pref_value.split('.')]

View File

@ -274,8 +274,9 @@ class VideoPuppeteer(object):
script_args=[self.video])
def __str__(self):
messages = ['%s - test url: %s: {' % (type(self).__name__,
self.test_url)]
messages = ['{} - test url: {}: '
.format(type(self).__name__, self.test_url)]
messages += '{'
if self.video:
messages += [
'\t(video)',
@ -283,6 +284,7 @@ class VideoPuppeteer(object):
'\tduration: {},'.format(self.duration),
'\texpected_duration: {},'.format(self.expected_duration),
'\tplayed: {}'.format(self.played),
'\tinterval: {}'.format(self.interval),
'\tlag: {},'.format(self.lag),
'\turl: {}'.format(self.video_url),
'\tsrc: {}'.format(self.video_src),
@ -313,8 +315,10 @@ class TimeRanges:
self.ranges = [(pair[0], pair[1]) for pair in ranges]
def __repr__(self):
return 'TimeRanges: length: {}, ranges: {}'\
.format(self.length, self.ranges)
return (
'TimeRanges: length: {}, ranges: {}'
.format(self.length, self.ranges)
)
def start(self, index):
return self.ranges[index][0]
@ -333,9 +337,11 @@ def playback_started(video):
"""
try:
played_ranges = video.played
return played_ranges.length > 0 and \
played_ranges.start(0) < played_ranges.end(0) and \
played_ranges.end(0) > 0.0
return (
played_ranges.length > 0 and
played_ranges.start(0) < played_ranges.end(0) and
played_ranges.end(0) > 0.0
)
except Exception as e:
print ('Got exception {}'.format(e))
return False
@ -358,8 +364,8 @@ def playback_done(video):
# Check to see if the video has stalled. Accumulate the amount of lag
# since the video started, and if it is too high, then raise.
if video.stall_wait_time and (video.lag > video.stall_wait_time):
raise VideoException('Video %s stalled.\n%s' % (video.video_url,
video))
raise VideoException('Video {} stalled.\n{}'
.format(video.video_url, video))
# We are cruising, so we are not done.
return False

View File

@ -340,8 +340,12 @@ class YouTubePuppeteer(VideoPuppeteer):
ad_timeout = (self.search_ad_duration() or 30) + 5
wait = Wait(self, timeout=ad_timeout, interval=1)
try:
self.marionette.log('process_ad: waiting %s s for ad' % ad_timeout)
verbose_until(wait, self, lambda y: y.ad_ended, "Check if ad ended")
self.marionette.log('process_ad: waiting {} s for ad'
.format(ad_timeout))
verbose_until(wait,
self,
lambda y: y.ad_ended,
"Check if ad ended")
except TimeoutException:
self.marionette.log('Waiting for ad to end timed out',
level='WARNING')
@ -372,7 +376,7 @@ class YouTubePuppeteer(VideoPuppeteer):
return True
except (TimeoutException, NoSuchElementException):
self.marionette.log('Could not obtain '
'element: %s' % selector,
'element: {}'.format(selector),
level='WARNING')
return False
@ -402,7 +406,7 @@ class YouTubePuppeteer(VideoPuppeteer):
return 60 * ad_minutes + ad_seconds
except (TimeoutException, NoSuchElementException):
self.marionette.log('Could not obtain '
'element: %s' % selector,
'element: {}'.format(selector),
level='WARNING')
return None
@ -462,7 +466,7 @@ class YouTubePuppeteer(VideoPuppeteer):
script_args=[checkbox])
self.marionette.log('Toggled autoplay.')
autoplay = get_status(checkbox)
self.marionette.log('Autoplay is %s' % autoplay)
self.marionette.log('Autoplay is {}'.format(autoplay))
return (autoplay is not None) and (not autoplay)
except (NoSuchElementException, TimeoutException):
return False
@ -474,13 +478,13 @@ class YouTubePuppeteer(VideoPuppeteer):
ad_state = self._yt_player_state_name[self.ad_state]
messages += [
'.html5-media-player: {',
'\tvideo id: {0},'.format(self.movie_id),
'\tvideo_title: {0}'.format(self.movie_title),
'\tcurrent_state: {0},'.format(player_state),
'\tad_state: {0},'.format(ad_state),
'\tplayback_quality: {0},'.format(self.playback_quality),
'\tcurrent_time: {0},'.format(self.player_current_time),
'\tduration: {0},'.format(self.player_duration),
'\tvideo id: {},'.format(self.movie_id),
'\tvideo_title: {}'.format(self.movie_title),
'\tcurrent_state: {},'.format(player_state),
'\tad_state: {},'.format(ad_state),
'\tplayback_quality: {},'.format(self.playback_quality),
'\tcurrent_time: {},'.format(self.player_current_time),
'\tduration: {},'.format(self.player_duration),
'}'
]
else:

View File

@ -2,7 +2,11 @@
# 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/.
from external_media_harness.testcase import MediaTestCase, VideoPlaybackTestsMixin, EMESetupMixin
from external_media_harness.testcase import (
MediaTestCase,
VideoPlaybackTestsMixin,
EMESetupMixin
)
class TestEMEPlayback(MediaTestCase, VideoPlaybackTestsMixin, EMESetupMixin):

View File

@ -9,8 +9,11 @@ from marionette_driver.errors import TimeoutException
from external_media_tests.utils import verbose_until
from external_media_harness.testcase import MediaTestCase
from external_media_tests.media_utils.video_puppeteer import VideoException
from external_media_tests.media_utils.youtube_puppeteer import (YouTubePuppeteer, playback_done,
wait_for_almost_done)
from external_media_tests.media_utils.youtube_puppeteer import (
YouTubePuppeteer,
playback_done,
wait_for_almost_done
)
class TestBasicYouTubePlayback(MediaTestCase):
@ -33,8 +36,8 @@ class TestBasicYouTubePlayback(MediaTestCase):
for url in self.video_urls:
self.logger.info(url)
youtube = YouTubePuppeteer(self.marionette, url)
self.logger.info('Expected duration: %s' %
youtube.expected_duration)
self.logger.info('Expected duration: {}'
.format(youtube.expected_duration))
youtube.deactivate_autoplay()
final_piece = 60
@ -45,15 +48,17 @@ class TestBasicYouTubePlayback(MediaTestCase):
raise self.failureException(e)
duration = abs(youtube.expected_duration) + 1
if duration > 1:
self.logger.info('Almost done: %s - %s seconds left.' %
(youtube.movie_id, time_left))
self.logger.info('Almost done: {} - {} seconds left.'
.format(youtube.movie_id, time_left))
if time_left > final_piece:
self.marionette.log('time_left greater than '
'final_piece - %s' % time_left,
'final_piece - {}'
.format(time_left),
level='WARNING')
self.save_screenshot()
else:
self.marionette.log('Duration close to 0 - %s' % youtube,
self.marionette.log('Duration close to 0 - {}'
.format(youtube),
level='WARNING')
self.save_screenshot()
try:

View File

@ -35,6 +35,7 @@ function assertUint8ArraysEqual(a, b, comparingWhat) {
*/
function listenForEventsOnSocket(socket, socketType) {
let wantDataLength = null;
let wantDataAndClose = false;
let pendingResolve = null;
let receivedEvents = [];
let receivedData = null;
@ -51,7 +52,16 @@ function listenForEventsOnSocket(socket, socketType) {
socket.onopen = handleGenericEvent;
socket.ondrain = handleGenericEvent;
socket.onerror = handleGenericEvent;
socket.onclose = handleGenericEvent;
socket.onclose = function(event) {
if (!wantDataAndClose) {
handleGenericEvent(event);
} else if (pendingResolve) {
dump('(' + socketType + ' event: close)\n');
pendingResolve(receivedData);
pendingResolve = null;
wantDataAndClose = false;
}
}
socket.ondata = function(event) {
dump('(' + socketType + ' event: ' + event.type + ' length: ' +
event.data.byteLength + ')\n');
@ -114,6 +124,19 @@ function listenForEventsOnSocket(socket, socketType) {
pendingResolve = resolve;
wantDataLength = length;
});
},
waitForAnyDataAndClose: function() {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
// we may receive no data before getting close, in which case we want to
// return an empty array
receivedData = new Uint8Array();
wantDataAndClose = true;
});
}
};
}
@ -379,13 +402,10 @@ function* test_basics() {
'Server sending more than 64k should result in the buffer being full.');
clientSocket.closeImmediately();
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(1);
serverReceived = yield serverQueue.waitForAnyDataAndClose();
is(serverReceived.length < (2 * bigUint8Array.length), true, 'Received array length less than sent array length');
is((yield serverQueue.waitForEvent()).type, 'close',
'The close event is received after calling closeImmediately');
// -- Close the listening server (and try to connect)
// We want to verify that the server actually closes / stops listening when
// we tell it to.

View File

@ -284,6 +284,50 @@ const gTests = {
setOverrideDPPX(dppx);
setFullZoom(zoom);
},
"test OverrideDPPX is kept on document navigation": (done) => {
assertValuesAreInitial();
let frameOriginalFontSize = getBodyFontSize(frameWindow);
let frameStyle = createFontStyleForDPPX(frameWindow.document, dppx, "32");
let frameCurrentFontSize = getBodyFontSize(frameWindow);
is(frameCurrentFontSize, frameOriginalFontSize,
"frame's media queries are not applied yet");
setOverrideDPPX(dppx);
frameCurrentFontSize = getBodyFontSize(frameWindow);
is(frameWindow.devicePixelRatio, dppx,
"frame's devicePixelRatio overridden.");
isnot(frameCurrentFontSize, frameOriginalFontSize,
"frame's media queries are applied.");
is(frameCurrentFontSize, "32px",
"frame's font size has the expected value.");
frameWindow.frameElement.addEventListener("load", function listener() {
this.removeEventListener("load", listener);
frameStyle = createFontStyleForDPPX(frameWindow.document, dppx, "32");
frameCurrentFontSize = getBodyFontSize(frameWindow);
is(frameWindow.devicePixelRatio, dppx,
"frame's devicePixelRatio is still overridden.");
isnot(frameCurrentFontSize, frameOriginalFontSize,
"frame's media queries are still applied.");
is(frameCurrentFontSize, "32px",
"frame's font size has still the expected value.");
frameStyle.remove();
setOverrideDPPX(0);
done();
});
frameWindow.location.reload(true);
}
};

View File

@ -45,6 +45,10 @@ VRDisplayHost::AddLayer(VRLayerParent *aLayer)
StartPresentation();
}
mDisplayInfo.mIsPresenting = mLayers.Length() > 0;
// Ensure that the content process receives the change immediately
VRManager* vm = VRManager::Get();
vm->RefreshVRDisplays();
}
void
@ -55,6 +59,10 @@ VRDisplayHost::RemoveLayer(VRLayerParent *aLayer)
StopPresentation();
}
mDisplayInfo.mIsPresenting = mLayers.Length() > 0;
// Ensure that the content process receives the change immediately
VRManager* vm = VRManager::Get();
vm->RefreshVRDisplays();
}
#if defined(XP_WIN)

View File

@ -5237,7 +5237,7 @@ ScrollFrameHelper::ReflowFinished()
// do anything.
nsPoint currentScrollPos = GetScrollPosition();
ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
if (!mAsyncScroll && !mAsyncSmoothMSDScroll) {
if (!mAsyncScroll && !mAsyncSmoothMSDScroll && !mApzSmoothScrollDestination) {
// We need to have mDestination track the current scroll position,
// in case it falls outside the new reflow area. mDestination is used
// by ScrollBy as its starting position.

View File

@ -14,15 +14,12 @@ from mach.decorators import (
def run_reftest(context, **kwargs):
kwargs['app'] = kwargs['app'] or context.firefox_bin
kwargs['e10s'] = context.mozharness_config.get('e10s', kwargs['e10s'])
kwargs['certPath'] = context.certs_dir
kwargs['utilityPath'] = context.bin_dir
kwargs['extraProfileFiles'].append(os.path.join(context.bin_dir, 'plugins'))
if not kwargs['app']:
# This could still return None in which case --appname must be used
# to specify the firefox binary.
kwargs['app'] = context.find_firefox()
if not kwargs['tests']:
kwargs['tests'] = [os.path.join('layout', 'reftests', 'reftest.list')]

View File

@ -198,6 +198,7 @@ dependencies {
compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"

View File

@ -64,6 +64,7 @@ JAVA_CLASSPATH += \
$(ANDROID_DESIGN_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(ANDROID_CUSTOMTABS_AAR_LIB) \
$(ANDROID_PALETTE_V7_AAR_LIB) \
$(NULL)
# If native devices are enabled, add Google Play Services and some of the v7
@ -106,6 +107,7 @@ java_bundled_libs := \
$(ANDROID_DESIGN_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(ANDROID_CUSTOMTABS_AAR_LIB) \
$(ANDROID_PALETTE_V7_AAR_LIB) \
$(NULL)
ifdef MOZ_NATIVE_DEVICES
@ -396,6 +398,7 @@ generated/android/support/design/R.java: .aapt.deps ;
generated/android/support/v7/mediarouter/R.java: .aapt.deps ;
generated/android/support/v7/recyclerview/R.java: .aapt.deps ;
generated/android/support/customtabs/R.java: .aapt.deps ;
generated/android/support/v7/palette/R.java: .aapt.deps ;
generated/com/google/android/gms/R.java: .aapt.deps ;
generated/com/google/android/gms/ads/R.java: .aapt.deps ;
generated/com/google/android/gms/base/R.java: .aapt.deps ;

View File

@ -1211,6 +1211,10 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
String[] selectionArgs, String sortOrder) {
final int match = URI_MATCHER.match(uri);
// Handle only queries requiring a writable DB connection here: most queries need only a readable
// connection, hence we can get a readable DB once, and then handle most queries within a switch.
// TopSites requires a writable connection (because of the temporary tables it uses), hence
// we handle that separately, i.e. before retrieving a readable connection.
if (match == TOPSITES) {
if (uri.getBooleanQueryParameter(BrowserContract.PARAM_TOPSITES_DISABLE_PINNED, false)) {
return getPlainTopSites(uri);

View File

@ -5,15 +5,17 @@
package org.mozilla.gecko.icons;
import android.support.annotation.NonNull;
import org.mozilla.gecko.icons.loader.ContentProviderLoader;
import org.mozilla.gecko.icons.loader.DataUriLoader;
import org.mozilla.gecko.icons.loader.DiskLoader;
import org.mozilla.gecko.icons.loader.IconDownloader;
import org.mozilla.gecko.icons.loader.IconGenerator;
import org.mozilla.gecko.icons.loader.IconLoader;
import org.mozilla.gecko.icons.loader.JarLoader;
import org.mozilla.gecko.icons.loader.LegacyLoader;
import org.mozilla.gecko.icons.loader.IconLoader;
import org.mozilla.gecko.icons.loader.MemoryLoader;
import org.mozilla.gecko.icons.loader.DiskLoader;
import org.mozilla.gecko.icons.preparation.AboutPagesPreparer;
import org.mozilla.gecko.icons.preparation.AddDefaultIconUrl;
import org.mozilla.gecko.icons.preparation.FilterKnownFailureUrls;
@ -22,17 +24,19 @@ import org.mozilla.gecko.icons.preparation.FilterPrivilegedUrls;
import org.mozilla.gecko.icons.preparation.LookupIconUrl;
import org.mozilla.gecko.icons.preparation.Preparer;
import org.mozilla.gecko.icons.processing.ColorProcessor;
import org.mozilla.gecko.icons.processing.DiskProcessor;
import org.mozilla.gecko.icons.processing.MemoryProcessor;
import org.mozilla.gecko.icons.processing.Processor;
import org.mozilla.gecko.icons.processing.ResizingProcessor;
import org.mozilla.gecko.icons.processing.DiskProcessor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Executor for icon requests.
@ -102,20 +106,40 @@ import java.util.concurrent.Future;
* Ordered list of processors that run after an icon has been loaded.
*/
private static final List<Processor> PROCESSORS = Arrays.asList(
// Extract the dominant color from the icon
new ColorProcessor(),
// Store the icon (and mapping) in the disk cache if needed
new DiskProcessor(),
// Resize the icon to match the target size (if possible)
new ResizingProcessor(),
// Extract the dominant color from the icon
new ColorProcessor(),
// Store the icon in the memory cache
new MemoryProcessor()
);
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
private static final ExecutorService EXECUTOR;
static {
final ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable runnable) {
Thread thread = new Thread(runnable, "GeckoIconTask");
thread.setDaemon(false);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
};
// Single thread executor
EXECUTOR = new ThreadPoolExecutor(
1, /* corePoolSize */
1, /* maximumPoolSize */
0L, /* keepAliveTime */
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
factory);
}
/**
* Submit the request for execution.

View File

@ -56,7 +56,7 @@ public class IconDownloader implements IconLoader {
}
try {
LoadFaviconResult result = downloadAndDecodeImage(request.getContext(), iconUrl);
final LoadFaviconResult result = downloadAndDecodeImage(request.getContext(), iconUrl);
if (result == null) {
return null;
}
@ -90,7 +90,7 @@ public class IconDownloader implements IconLoader {
@VisibleForTesting
LoadFaviconResult downloadAndDecodeImage(Context context, String targetFaviconURL) throws IOException, URISyntaxException {
// Try the URL we were given.
HttpURLConnection connection = tryDownload(targetFaviconURL);
final HttpURLConnection connection = tryDownload(targetFaviconURL);
if (connection == null) {
return null;
}
@ -99,7 +99,8 @@ public class IconDownloader implements IconLoader {
// Decode the image from the fetched response.
try {
return decodeImageFromResponse(context, connection.getInputStream(), connection.getHeaderFieldInt("Content-Length", -1));
stream = connection.getInputStream();
return decodeImageFromResponse(context, stream, connection.getHeaderFieldInt("Content-Length", -1));
} finally {
// Close the stream and free related resources.
IOUtils.safeStreamClose(stream);
@ -114,7 +115,7 @@ public class IconDownloader implements IconLoader {
* @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
*/
private HttpURLConnection tryDownload(String faviconURI) throws URISyntaxException, IOException {
HashSet<String> visitedLinkSet = new HashSet<>();
final HashSet<String> visitedLinkSet = new HashSet<>();
visitedLinkSet.add(faviconURI);
return tryDownloadRecurse(faviconURI, visitedLinkSet);
}
@ -127,10 +128,10 @@ public class IconDownloader implements IconLoader {
return null;
}
HttpURLConnection connection = connectTo(faviconURI);
final HttpURLConnection connection = connectTo(faviconURI);
// Was the response a failure?
int status = connection.getResponseCode();
final int status = connection.getResponseCode();
// Handle HTTP status codes requesting a redirect.
if (status >= 300 && status < 400) {
@ -168,7 +169,7 @@ public class IconDownloader implements IconLoader {
@VisibleForTesting
HttpURLConnection connectTo(String faviconURI) throws URISyntaxException, IOException {
HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(
final HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(
new URI(faviconURI));
connection.setRequestProperty("User-Agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
@ -196,7 +197,7 @@ public class IconDownloader implements IconLoader {
*/
private LoadFaviconResult decodeImageFromResponse(Context context, InputStream stream, int contentLength) throws IOException {
// This may not be provided, but if it is, it's useful.
int bufferSize;
final int bufferSize;
if (contentLength > 0) {
// The size was reported and sane, so let's use that.
// Integer overflow should not be a problem for Favicon sizes...
@ -207,7 +208,7 @@ public class IconDownloader implements IconLoader {
}
// Read the InputStream into a byte[].
IOUtils.ConsumedInputStream result = IOUtils.readFully(stream, bufferSize);
final IOUtils.ConsumedInputStream result = IOUtils.readFully(stream, bufferSize);
if (result == null) {
return null;
}

View File

@ -25,10 +25,23 @@ import org.mozilla.gecko.icons.IconResponse;
public class LegacyLoader implements IconLoader {
@Override
public IconResponse load(IconRequest request) {
if (!request.shouldSkipNetwork()) {
// If we are allowed to load from the network for this request then just ommit the legacy
// loader and fetch a fresh new icon.
return null;
}
if (request.shouldSkipDisk()) {
return null;
}
if (request.getIconCount() > 1) {
// There are still other icon URLs to try. Let's try to load from the legacy loader only
// if there's one icon left and the other loads have failed. We will ignore the icon URL
// anyways and try to receive the legacy icon URL from the database.
return null;
}
final Bitmap bitmap = loadBitmapFromDatabase(request);
if (bitmap == null) {
@ -41,7 +54,7 @@ public class LegacyLoader implements IconLoader {
/* package-private */ Bitmap loadBitmapFromDatabase(IconRequest request) {
final Context context = request.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final BrowserDB db = GeckoProfile.get(request.getContext()).getDB();
final BrowserDB db = GeckoProfile.get(context).getDB();
// We ask the database for the favicon URL and ignore the icon URL in the request object:
// As we are not updating the database anymore the icon might be stored under a different URL.

View File

@ -28,7 +28,7 @@ public class FilterMimeTypes implements Preparer {
if (TextUtils.isEmpty(mimeType)) {
// We do not have a MIME type for this icon, so we cannot know in advance if we are able
// to decode it. Let's just continue.
return;
continue;
}
if (!IconsHelper.canDecodeType(mimeType)) {

View File

@ -5,7 +5,8 @@
package org.mozilla.gecko.icons.processing;
import org.mozilla.gecko.gfx.BitmapUtils;
import android.support.v7.graphics.Palette;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.IconResponse;
@ -14,12 +15,16 @@ import org.mozilla.gecko.icons.IconResponse;
* response object.
*/
public class ColorProcessor implements Processor {
private static final int DEFAULT_COLOR = 0; // 0 == No color
@Override
public void process(IconRequest request, IconResponse response) {
if (response.hasColor()) {
return;
}
response.updateColor(BitmapUtils.getDominantColor(response.getBitmap()));
final Palette palette = Palette.from(response.getBitmap()).generate();
response.updateColor(palette.getVibrantColor(DEFAULT_COLOR));
}
}

View File

@ -65,6 +65,9 @@ public class FaviconView extends ImageView {
// boolean switch for overriding scaletype, whose value is defined in attrs.xml .
private final boolean isOverrideScaleTypeEnabled;
// boolean switch for disabling rounded corners, value defined in attrs.xml .
private final boolean areRoundCornersEnabled;
// Initializing the static paints.
static {
sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@ -78,6 +81,7 @@ public class FaviconView extends ImageView {
try {
isDominantBorderEnabled = a.getBoolean(R.styleable.FaviconView_dominantBorderEnabled, true);
isOverrideScaleTypeEnabled = a.getBoolean(R.styleable.FaviconView_overrideScaleType, true);
areRoundCornersEnabled = a.getBoolean(R.styleable.FaviconView_enableRoundCorners, true);
} finally {
a.recycle();
}
@ -115,7 +119,11 @@ public class FaviconView extends ImageView {
if (isDominantBorderEnabled) {
sBackgroundPaint.setColor(mDominantColor & 0x7FFFFFFF);
canvas.drawRoundRect(mBackgroundRect, mBackgroundCornerRadius, mBackgroundCornerRadius, sBackgroundPaint);
if (areRoundCornersEnabled) {
canvas.drawRoundRect(mBackgroundRect, mBackgroundCornerRadius, mBackgroundCornerRadius, sBackgroundPaint);
} else {
canvas.drawRect(mBackgroundRect, sBackgroundPaint);
}
}
super.onDraw(canvas);

View File

@ -80,6 +80,11 @@ if CONFIG['ANDROID_CUSTOMTABS_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.customtabs']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_CUSTOMTABS_AAR_RES']]
resjar.generated_sources += ['android/support/customtabs/R.java']
if CONFIG['ANDROID_PALETTE_V7_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v7.palette']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PALETTE_V7_AAR_RES']]
resjar.generated_sources += ['android/support/v7/palette/R.java']
resjar.javac_flags += ['-Xlint:all']
@ -881,6 +886,7 @@ gbjar.extra_jars += [CONFIG['ANDROID_CARDVIEW_V7_AAR_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_DESIGN_AAR_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_CUSTOMTABS_AAR_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_PALETTE_V7_AAR_LIB']]
gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="115dp"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_margin="1dp">
<RelativeLayout
@ -18,7 +19,8 @@
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
tools:background="@drawable/favicon_globe"/>
tools:background="@drawable/favicon_globe"
gecko:enableRoundCorners="false"/>
<TextView
android:id="@+id/title"

View File

@ -173,6 +173,7 @@
<declare-styleable name="FaviconView">
<attr name="dominantBorderEnabled" format="boolean" />
<attr name="overrideScaleType" format="boolean" />
<attr name="enableRoundCorners" format="boolean"/>
</declare-styleable>
<declare-styleable name="OverlayDialogButton">

View File

@ -72,23 +72,14 @@
"size": 51955340
},
{
"version": "rustc 1.8.0 (db2939409 2016-04-11)",
"size": 123218320,
"digest": "7afcd7b39c4d5277db6b28951602aff4c698102ba45d3d811b353ca7446074beceebf03a2a529e323af19d73db4acbe96ec2bdad44def2e218ed36f55e82cab2",
"version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
"size": 97552448,
"digest": "272438c1692a46998dc44f22bd1fe18da1be7af2e7fdcf6c52709366c80c73e30637f0c3864f45c64edf46ce6a905538c14b2313983be973f9f29a2f191ec89b",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"version": "rust stdlib repack 1.8.0 (db2939409 2016-04-11)",
"size": 17874585,
"visibility": "public",
"digest": "bea72d352a70411240d95c7ab33e97d85be9cf0548807968dc721b14d816c70f4fd77a0ca1b43b851772ec06c0c1395b474ad342d506f8987fdbb070393c81f6",
"algorithm": "sha512",
"filename": "rust-std-lib.tar.bz2",
"unpack": true
},
{
"algorithm": "sha512",
"visibility": "public",
"filename": "dotgradle.tar.xz",

View File

@ -15,6 +15,8 @@ import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.robolectric.RuntimeEnvironment;
import java.util.Iterator;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@ -25,9 +27,27 @@ import static org.mockito.Mockito.verify;
public class TestLegacyLoader {
private static final String TEST_PAGE_URL = "http://www.mozilla.org";
private static final String TEST_ICON_URL = "https://example.org/favicon.ico";
private static final String TEST_ICON_URL_2 = "https://example.com/page/favicon.ico";
private static final String TEST_ICON_URL_3 = "https://example.net/icon/favicon.ico";
@Test
public void testDatabaseIsQueriesForNormalRequests() {
public void testDatabaseIsQueriesForNormalRequestsWithNetworkSkipped() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
.skipNetwork()
.build();
final LegacyLoader loader = spy(new LegacyLoader());
final IconResponse response = loader.load(request);
verify(loader).loadBitmapFromDatabase(request);
Assert.assertNull(response);
}
@Test
public void testNothingIsLoadedIfNetworkIsNotSkipped() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
@ -36,7 +56,7 @@ public class TestLegacyLoader {
final LegacyLoader loader = spy(new LegacyLoader());
final IconResponse response = loader.load(request);
verify(loader).loadBitmapFromDatabase(request);
verify(loader, never()).loadBitmapFromDatabase(request);
Assert.assertNull(response);
}
@ -62,6 +82,7 @@ public class TestLegacyLoader {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
.skipNetwork()
.build();
final Bitmap bitmap = mock(Bitmap.class);
@ -74,4 +95,37 @@ public class TestLegacyLoader {
Assert.assertNotNull(response);
Assert.assertEquals(bitmap, response.getBitmap());
}
@Test
public void testLoaderOnlyLoadsIfThereIsOneIconLeft() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_2))
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_3))
.skipNetwork()
.build();
final LegacyLoader loader = spy(new LegacyLoader());
doReturn(mock(Bitmap.class)).when(loader).loadBitmapFromDatabase(request);
// First load doesn't load an icon.
Assert.assertNull(loader.load(request));
// Second load doesn't load an icon.
removeFirstIcon(request);
Assert.assertNull(loader.load(request));
// Now only one icon is left and a response will be returned.
removeFirstIcon(request);
Assert.assertNotNull(loader.load(request));
}
private void removeFirstIcon(IconRequest request) {
final Iterator<IconDescriptor> iterator = request.getIconIterator();
if (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
}

View File

@ -33,7 +33,7 @@ public class TestColorProcessor {
processor.process(null, response);
Assert.assertTrue(response.hasColor());
Assert.assertEquals(Color.RED, response.getColor());
Assert.assertEquals(0xFFF80000, response.getColor());
}
private Bitmap createRedBitmapMock() {

View File

@ -245,6 +245,17 @@ def possible_makes(make, host):
check_prog('GMAKE', possible_makes)
# tup detection
# ==============================================================
@depends(build_backends)
def tup_progs(build_backends):
for backend in build_backends:
if 'Tup' in backend:
return ['tup']
return None
tup = check_prog('TUP', tup_progs)
# Miscellaneous programs
# ==============================================================
check_prog('DOXYGEN', ('doxygen',), allow_missing=True)

View File

@ -1632,7 +1632,7 @@ Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
nsCOMPtr<nsIURI> uri;
uint32_t hitCount, lastHit, flags;
if (!ParseMetaDataEntry(key, value, getter_AddRefs(uri), hitCount, lastHit, flags)) {
if (!ParseMetaDataEntry(nullptr, value, nullptr, hitCount, lastHit, flags)) {
// This failed, get rid of it so we don't waste space
entry->SetMetaDataElement(key, nullptr);
continue;
@ -1672,10 +1672,8 @@ Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
return NS_OK;
}
nsCOMPtr<nsIURI> parsedURI;
uint32_t hitCount, lastHit, flags;
bool ok = mPredictor->ParseMetaDataEntry(key, value,
getter_AddRefs(parsedURI),
bool ok = mPredictor->ParseMetaDataEntry(nullptr, value, nullptr,
hitCount, lastHit, flags);
if (!ok) {
@ -1686,11 +1684,9 @@ Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
return NS_OK;
}
nsCString uri;
nsresult rv = parsedURI->GetAsciiSpec(uri);
nsCString uri(key + (sizeof(META_DATA_PREFIX) - 1));
uint32_t uriLength = uri.Length();
if (NS_SUCCEEDED(rv) &&
uriLength > mPredictor->mMaxURILength) {
if (uriLength > mPredictor->mMaxURILength) {
// Default to getting rid of URIs that are too long and were put in before
// we had our limit on URI length, in order to free up some space.
nsCString nsKey;

View File

@ -280,6 +280,7 @@ nvFIFO::operator[] (size_t index) const
Http2BaseCompressor::Http2BaseCompressor()
: mOutput(nullptr)
, mMaxBuffer(kDefaultMaxBuffer)
, mMaxBufferSetting(kDefaultMaxBuffer)
{
mDynamicReporter = new HpackDynamicTableReporter(this);
RegisterStrongMemoryReporter(mDynamicReporter);
@ -342,6 +343,23 @@ Http2BaseCompressor::DumpState()
}
}
void
Http2BaseCompressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize)
{
MOZ_ASSERT(maxBufferSize <= mMaxBufferSetting);
uint32_t removedCount = 0;
LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called", maxBufferSize));
while (mHeaderTable.VariableLength() && (mHeaderTable.ByteCount() > maxBufferSize)) {
mHeaderTable.RemoveElement();
++removedCount;
}
mMaxBuffer = maxBufferSize;
}
nsresult
Http2Decompressor::DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
nsACString &output, bool isPush)
@ -949,14 +967,30 @@ Http2Decompressor::DoContextUpdate()
// This starts with 001 bit pattern
MOZ_ASSERT((mData[mOffset] & 0xE0) == 0x20);
// Getting here means we have to adjust the max table size
// Getting here means we have to adjust the max table size, because the
// compressor on the other end has signaled to us through HPACK (not H2)
// that it's using a size different from the currently-negotiated size.
// This change could either come about because we've sent a
// SETTINGS_HEADER_TABLE_SIZE, or because the encoder has decided that
// the current negotiated size doesn't fit its needs (for whatever reason)
// and so it needs to change it (either up to the max allowed by our SETTING,
// or down to some value below that)
uint32_t newMaxSize;
nsresult rv = DecodeInteger(5, newMaxSize);
LOG(("Http2Decompressor::DoContextUpdate new maximum size %u", newMaxSize));
if (NS_FAILED(rv)) {
return rv;
}
return mCompressor->SetMaxBufferSizeInternal(newMaxSize);
if (newMaxSize > mMaxBufferSetting) {
// This is fatal to the session - peer is trying to use a table larger
// than we have made available.
return NS_ERROR_FAILURE;
}
SetMaxBufferSizeInternal(newMaxSize);
return NS_OK;
}
/////////////////////////////////////////////////////////////////
@ -1359,26 +1393,5 @@ Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize)
}
}
nsresult
Http2Compressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize)
{
if (maxBufferSize > mMaxBufferSetting) {
return NS_ERROR_FAILURE;
}
uint32_t removedCount = 0;
LOG(("Http2Compressor::SetMaxBufferSizeInternal %u called", maxBufferSize));
while (mHeaderTable.VariableLength() && (mHeaderTable.ByteCount() > maxBufferSize)) {
mHeaderTable.RemoveElement();
++removedCount;
}
mMaxBuffer = maxBufferSize;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@ -72,11 +72,13 @@ protected:
virtual void ClearHeaderTable();
virtual void MakeRoom(uint32_t amount, const char *direction);
virtual void DumpState();
virtual void SetMaxBufferSizeInternal(uint32_t maxBufferSize);
nsACString *mOutput;
nvFIFO mHeaderTable;
uint32_t mMaxBuffer;
uint32_t mMaxBufferSetting;
private:
RefPtr<HpackDynamicTableReporter> mDynamicReporter;
@ -99,7 +101,6 @@ public:
void GetScheme(nsACString &hdr) { hdr = mHeaderScheme; }
void GetPath(nsACString &hdr) { hdr = mHeaderPath; }
void GetMethod(nsACString &hdr) { hdr = mHeaderMethod; }
void SetCompressor(Http2Compressor *compressor) { mCompressor = compressor; }
private:
nsresult DoIndexed();
@ -122,8 +123,6 @@ private:
nsresult DecodeFinalHuffmanCharacter(const HuffmanIncomingTable *table,
uint8_t &c, uint8_t &bitsLeft);
Http2Compressor *mCompressor;
nsCString mHeaderStatus;
nsCString mHeaderHost;
nsCString mHeaderScheme;
@ -143,7 +142,6 @@ class Http2Compressor final : public Http2BaseCompressor
{
public:
Http2Compressor() : mParsedContentLength(-1),
mMaxBufferSetting(kDefaultMaxBuffer),
mBufferSizeChangeWaiting(false),
mLowestBufferSizeWaiting(0)
{ };
@ -159,7 +157,6 @@ public:
int64_t GetParsedContentLength() { return mParsedContentLength; } // -1 on not found
void SetMaxBufferSize(uint32_t maxBufferSize);
nsresult SetMaxBufferSizeInternal(uint32_t maxBufferSize);
private:
enum outputCode {
@ -178,7 +175,6 @@ private:
void EncodeTableSizeChange(uint32_t newMaxSize);
int64_t mParsedContentLength;
uint32_t mMaxBufferSetting;
bool mBufferSizeChangeWaiting;
uint32_t mLowestBufferSizeWaiting;
};

View File

@ -123,7 +123,6 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t versio
mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
mDecompressBuffer.SetCapacity(kDefaultBufferSize);
mDecompressor.SetCompressor(&mCompressor);
mPushAllowance = gHttpHandler->SpdyPushAllowance();
mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);

View File

@ -10,6 +10,7 @@ backends = {
'FasterMake': 'mozbuild.backend.fastermake',
'FasterMake+RecursiveMake': None,
'RecursiveMake': 'mozbuild.backend.recursivemake',
'Tup': 'mozbuild.backend.tup',
'VisualStudio': 'mozbuild.backend.visualstudio',
}

View File

@ -194,6 +194,15 @@ class BuildBackend(LoggingMixin):
def consume_finished(self):
"""Called when consume() has completed handling all objects."""
def build(self, config, output, jobs, verbose):
"""Called when 'mach build' is executed.
This should return the status value of a subprocess, where 0 denotes
success and any other value is an error code. A return value of None
indicates that the default 'make -f client.mk' should run.
"""
return None
@contextmanager
def _write_file(self, path=None, fh=None, mode='rU'):
"""Context manager to write a file.

View File

@ -0,0 +1,203 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, unicode_literals
import os
import mozpack.path as mozpath
from mozbuild.base import MozbuildObject
from mozbuild.backend.base import PartialBackend, HybridBackend
from mozbuild.backend.recursivemake import RecursiveMakeBackend
from mozbuild.shellutil import quote as shell_quote
from .common import CommonBackend
from ..frontend.data import (
ContextDerived,
)
from ..util import (
FileAvoidWrite,
)
class BackendTupfile(object):
"""Represents a generated Tupfile.
"""
def __init__(self, srcdir, objdir, environment, topsrcdir, topobjdir):
self.topsrcdir = topsrcdir
self.srcdir = srcdir
self.objdir = objdir
self.relobjdir = mozpath.relpath(objdir, topobjdir)
self.environment = environment
self.name = mozpath.join(objdir, 'Tupfile')
self.rules_included = False
self.fh = FileAvoidWrite(self.name, capture_diff=True)
self.fh.write('# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.\n')
self.fh.write('\n')
def write(self, buf):
self.fh.write(buf)
def include_rules(self):
if not self.rules_included:
self.write('include_rules\n')
self.rules_included = True
def rule(self, cmd, inputs=None, outputs=None, display=None, extra_outputs=None):
inputs = inputs or []
outputs = outputs or []
self.include_rules()
self.write(': %(inputs)s |> %(display)s%(cmd)s |> %(outputs)s%(extra_outputs)s\n' % {
'inputs': ' '.join(inputs),
'display': '^ %s^ ' % display if display else '',
'cmd': ' '.join(cmd),
'outputs': ' '.join(outputs),
'extra_outputs': ' | ' + ' '.join(extra_outputs) if extra_outputs else '',
})
def close(self):
return self.fh.close()
@property
def diff(self):
return self.fh.diff
class TupOnly(CommonBackend, PartialBackend):
"""Backend that generates Tupfiles for the tup build system.
"""
def _init(self):
CommonBackend._init(self)
self._backend_files = {}
self._cmd = MozbuildObject.from_environment()
def _get_backend_file(self, relativedir):
objdir = mozpath.join(self.environment.topobjdir, relativedir)
srcdir = mozpath.join(self.environment.topsrcdir, relativedir)
if objdir not in self._backend_files:
self._backend_files[objdir] = \
BackendTupfile(srcdir, objdir, self.environment,
self.environment.topsrcdir, self.environment.topobjdir)
return self._backend_files[objdir]
def consume_object(self, obj):
"""Write out build files necessary to build with tup."""
if not isinstance(obj, ContextDerived):
return False
consumed = CommonBackend.consume_object(self, obj)
# Even if CommonBackend acknowledged the object, we still need to let
# the RecursiveMake backend also handle these objects.
if consumed:
return False
return True
def consume_finished(self):
CommonBackend.consume_finished(self)
for objdir, backend_file in sorted(self._backend_files.items()):
with self._write_file(fh=backend_file):
pass
with self._write_file(mozpath.join(self.environment.topobjdir, 'Tuprules.tup')) as fh:
acdefines = [name for name in self.environment.defines
if not name in self.environment.non_global_defines]
acdefines_flags = ' '.join(['-D%s=%s' % (name,
shell_quote(self.environment.defines[name]))
for name in sorted(acdefines)])
fh.write('MOZ_OBJ_ROOT = $(TUP_CWD)\n')
fh.write('DIST = $(MOZ_OBJ_ROOT)/dist\n')
fh.write('ACDEFINES = %s\n' % acdefines_flags)
fh.write('topsrcdir = $(MOZ_OBJ_ROOT)/%s\n' % (
os.path.relpath(self.environment.topsrcdir, self.environment.topobjdir)
))
fh.write('PYTHON = $(MOZ_OBJ_ROOT)/_virtualenv/bin/python -B\n')
fh.write('PYTHON_PATH = $(PYTHON) $(topsrcdir)/config/pythonpath.py\n')
fh.write('PLY_INCLUDE = -I$(topsrcdir)/other-licenses/ply\n')
fh.write('IDL_PARSER_DIR = $(topsrcdir)/xpcom/idl-parser\n')
fh.write('IDL_PARSER_CACHE_DIR = $(MOZ_OBJ_ROOT)/xpcom/idl-parser\n')
# Run 'tup init' if necessary.
if not os.path.exists(mozpath.join(self.environment.topsrcdir, ".tup")):
tup = self.environment.substs.get('TUP', 'tup')
self._cmd.run_process(cwd=self.environment.topsrcdir, log_name='tup', args=[tup, 'init'])
def _handle_idl_manager(self, manager):
# TODO: This should come from GENERATED_FILES, and can be removed once
# those are implemented.
backend_file = self._get_backend_file('xpcom/idl-parser')
backend_file.rule(
display='python header.py -> [%o]',
cmd=[
'$(PYTHON_PATH)',
'$(PLY_INCLUDE)',
'$(topsrcdir)/xpcom/idl-parser/xpidl/header.py',
],
outputs=['xpidlyacc.py', 'xpidllex.py'],
)
backend_file = self._get_backend_file('xpcom/xpidl')
# These are used by mach/mixin/process.py to determine the current
# shell.
for var in ('SHELL', 'MOZILLABUILD', 'COMSPEC'):
backend_file.write('export %s\n' % var)
for module, data in sorted(manager.modules.iteritems()):
dest, idls = data
cmd = [
'$(PYTHON_PATH)',
'$(PLY_INCLUDE)',
'-I$(IDL_PARSER_DIR)',
'-I$(IDL_PARSER_CACHE_DIR)',
'$(topsrcdir)/python/mozbuild/mozbuild/action/xpidl-process.py',
'--cache-dir', '$(MOZ_OBJ_ROOT)/xpcom/idl-parser',
'$(DIST)/idl',
'$(DIST)/include',
'$(MOZ_OBJ_ROOT)/%s/components' % dest,
module,
]
cmd.extend(sorted(idls))
outputs = ['$(MOZ_OBJ_ROOT)/%s/components/%s.xpt' % (dest, module)]
outputs.extend(['$(MOZ_OBJ_ROOT)/dist/include/%s.h' % f for f in sorted(idls)])
backend_file.rule(
inputs=[
'$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidllex.py',
'$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidlyacc.py',
],
display='XPIDL %s' % module,
cmd=cmd,
outputs=outputs,
)
def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources,
unified_ipdl_cppsrcs_mapping):
# TODO: This isn't implemented yet in the tup backend, but it is called
# by the CommonBackend.
pass
def _handle_webidl_build(self, bindings_dir, unified_source_mapping,
webidls, expected_build_output_files,
global_define_files):
# TODO: This isn't implemented yet in the tup backend, but it is called
# by the CommonBackend.
pass
class TupBackend(HybridBackend(TupOnly, RecursiveMakeBackend)):
def build(self, config, output, jobs, verbose):
status = config._run_make(directory=self.environment.topobjdir, target='tup',
line_handler=output.on_line, log=False, print_directory=False,
ensure_exit_code=False, num_jobs=jobs, silent=not verbose,
append_env={b'NO_BUILDSTATUS_MESSAGES': b'1'})
return status

View File

@ -36,11 +36,10 @@ from mozbuild.base import (
ObjdirMismatchException,
)
from mozpack.manifests import (
InstallManifest,
from mozbuild.backend import (
backends,
get_backend_class,
)
from mozbuild.backend import backends
from mozbuild.shellutil import quote as shell_quote
@ -413,10 +412,39 @@ class Build(MachCommandBase):
if status != 0:
break
else:
status = self._run_make(srcdir=True, filename='client.mk',
line_handler=output.on_line, log=False, print_directory=False,
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
silent=not verbose)
# Try to call the default backend's build() method. This will
# run configure to determine BUILD_BACKENDS if it hasn't run
# yet.
config = None
try:
config = self.config_environment
except Exception:
config_rc = self.configure()
if config_rc != 0:
return config_rc
# Even if configure runs successfully, we may have trouble
# getting the config_environment for some builds, such as
# OSX Universal builds. These have to go through client.mk
# regardless.
try:
config = self.config_environment
except Exception:
pass
if config:
active_backend = config.substs.get('BUILD_BACKENDS', [None])[0]
if active_backend:
backend_cls = get_backend_class(active_backend)(config)
status = backend_cls.build(self, output, jobs, verbose)
# If the backend doesn't specify a build() method, then just
# call client.mk directly.
if status is None:
status = self._run_make(srcdir=True, filename='client.mk',
line_handler=output.on_line, log=False, print_directory=False,
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
silent=not verbose)
self.log(logging.WARNING, 'warning_summary',
{'count': len(monitor.warnings_database)},

View File

@ -782,7 +782,7 @@ BookmarksStore.prototype = {
return special;
return Async.promiseSpinningly(
PlacesSyncUtils.bookmarks.ensureGuidForId(id));
PlacesUtils.promiseItemGuid(id));
},
// noCreate is provided as an optional argument to prevent the creation of

View File

@ -0,0 +1,3 @@
[flake8]
max-line-length = 99
exclude = __init__.py,disti/*,build/*,session/*,marionette/runner/mixins/*, marionette/tests/*

View File

@ -11,12 +11,13 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import os
import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# sys.path.insert(0, os.path.abspath('.'))
here = os.path.dirname(os.path.abspath(__file__))
parent = os.path.dirname(here)
@ -25,7 +26,7 @@ sys.path.insert(0, parent)
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
@ -38,7 +39,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@ -52,43 +53,43 @@ copyright = u'2013, Mozilla Automation and Tools and individual contributors'
# built documents.
#
# The short X.Y version.
#version = '0'
# version = '0'
# The full version, including alpha/beta/rc tags.
#release = '0'
# release = '0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
@ -112,72 +113,72 @@ if not on_rtd:
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'MarionettePythonClientdoc'
@ -186,42 +187,42 @@ htmlhelp_basename = 'MarionettePythonClientdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'MarionettePythonClient.tex', u'Marionette Python Client Documentation',
u'Mozilla Automation and Tools team', 'manual'),
('index', 'MarionettePythonClient.tex', u'Marionette Python Client Documentation',
u'Mozilla Automation and Tools team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@ -234,7 +235,7 @@ man_pages = [
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@ -243,16 +244,16 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'MarionettePythonClient', u'Marionette Python Client Documentation',
u'Mozilla Automation and Tools team', 'MarionettePythonClient', 'One line description of project.',
'Miscellaneous'),
('index', 'MarionettePythonClient', 'Marionette Python Client Documentation',
'Mozilla Automation and Tools team', 'MarionettePythonClient',
'One line description of project.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'

View File

@ -16,16 +16,10 @@ import warnings
from marionette_driver.errors import (
MarionetteException, TimeoutException,
JavascriptException, NoSuchElementException, NoSuchWindowException,
StaleElementException, ScriptTimeoutException, ElementNotVisibleException,
NoSuchFrameException, InvalidElementStateException, NoAlertPresentException,
InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException,
MoveTargetOutOfBoundsException
)
from marionette_driver.marionette import Marionette
from marionette_driver.wait import Wait
from marionette_driver.expected import element_present, element_not_present
MarionetteException,
ScriptTimeoutException,
TimeoutException,
)
from mozlog import get_default_logger
@ -36,8 +30,10 @@ class SkipTest(Exception):
Usually you can use TestResult.skip() or one of the skipping decorators
instead of raising this directly.
"""
pass
class _ExpectedFailure(Exception):
"""
Raise this when a test is expected to fail.
@ -49,12 +45,13 @@ class _ExpectedFailure(Exception):
super(_ExpectedFailure, self).__init__()
self.exc_info = exc_info
class _UnexpectedSuccess(Exception):
"""
The test was supposed to fail, but it didn't!
"""
"""The test was supposed to fail, but it didn't."""
pass
def skip(reason):
"""Unconditionally skip a test."""
def decorator(test_item):
@ -69,6 +66,7 @@ def skip(reason):
return test_item
return decorator
def expectedFailure(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
@ -79,6 +77,7 @@ def expectedFailure(func):
raise _UnexpectedSuccess
return wrapper
def skip_if_chrome(target):
def wrapper(self, *args, **kwargs):
if self.marionette._send_message("getContext", key="value") == "chrome":
@ -86,6 +85,7 @@ def skip_if_chrome(target):
return target(self, *args, **kwargs)
return wrapper
def skip_if_desktop(target):
def wrapper(self, *args, **kwargs):
if self.marionette.session_capabilities.get('browserName') == 'firefox':
@ -93,6 +93,7 @@ def skip_if_desktop(target):
return target(self, *args, **kwargs)
return wrapper
def skip_if_mobile(target):
def wrapper(self, *args, **kwargs):
if self.marionette.session_capabilities.get('browserName') == 'fennec':
@ -100,6 +101,7 @@ def skip_if_mobile(target):
return target(self, *args, **kwargs)
return wrapper
def skip_if_e10s(target):
def wrapper(self, *args, **kwargs):
with self.marionette.using_context('chrome'):
@ -115,9 +117,9 @@ def skip_if_e10s(target):
return target(self, *args, **kwargs)
return wrapper
def skip_unless_protocol(predicate):
"""Given a predicate passed the current protocol level, skip the
test if the predicate does not match."""
"""Skip the test if the predicate does not match the current protocol level."""
def decorator(test_item):
@functools.wraps(test_item)
def skip_wrapper(self):
@ -128,6 +130,7 @@ def skip_unless_protocol(predicate):
return skip_wrapper
return decorator
def skip_unless_browser_pref(pref, predicate=bool):
"""
Skip a test based on the value of a browser preference.
@ -159,8 +162,9 @@ def skip_unless_browser_pref(pref, predicate=bool):
return wrapped
return wrapper
def parameterized(func_suffix, *args, **kwargs):
"""
r"""
A decorator that can generate methods given a base method and some data.
**func_suffix** is used as a suffix for the new created method and must be
@ -195,9 +199,11 @@ def parameterized(func_suffix, *args, **kwargs):
return func
return wrapped
def with_parameters(parameters):
"""
A decorator that can generate methods given a base method and some data.
Acts like :func:`parameterized`, but define all methods in one call.
Example::
@ -223,20 +229,26 @@ def with_parameters(parameters):
return func
return wrapped
def wraps_parameterized(func, func_suffix, args, kwargs):
"""Internal: for MetaParameterized"""
"""Internal: for MetaParameterized."""
def wrapper(self):
return func(self, *args, **kwargs)
wrapper.__name__ = func.__name__ + '_' + str(func_suffix)
wrapper.__doc__ = '[%s] %s' % (func_suffix, func.__doc__)
return wrapper
class MetaParameterized(type):
"""
A metaclass that allow a class to use decorators like :func:`parameterized`
A metaclass that allow a class to use decorators.
It can be used like :func:`parameterized`
or :func:`with_parameters` to generate new methods.
"""
RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]')
def __new__(cls, name, bases, attrs):
for k, v in attrs.items():
if callable(v) and hasattr(v, 'metaparameters'):
@ -245,18 +257,20 @@ class MetaParameterized(type):
wrapper = wraps_parameterized(v, func_suffix, args, kwargs)
if wrapper.__name__ in attrs:
raise KeyError("%s is already a defined method on %s" %
(wrapper.__name__, name))
(wrapper.__name__, name))
attrs[wrapper.__name__] = wrapper
del attrs[k]
return type.__new__(cls, name, bases, attrs)
class JSTest:
head_js_re = re.compile(r"MARIONETTE_HEAD_JS(\s*)=(\s*)['|\"](.*?)['|\"];")
context_re = re.compile(r"MARIONETTE_CONTEXT(\s*)=(\s*)['|\"](.*?)['|\"];")
timeout_re = re.compile(r"MARIONETTE_TIMEOUT(\s*)=(\s*)(\d+);")
inactivity_timeout_re = re.compile(r"MARIONETTE_INACTIVITY_TIMEOUT(\s*)=(\s*)(\d+);")
class CommonTestCase(unittest.TestCase):
__metaclass__ = MetaParameterized
@ -310,11 +324,11 @@ class CommonTestCase(unittest.TestCase):
testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
try:
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', ''))
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, skip_why)
finally:
result.stopTest(self)
@ -361,7 +375,8 @@ class CommonTestCase(unittest.TestCase):
if addUnexpectedSuccess is not None:
addUnexpectedSuccess(self)
else:
warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
warnings.warn("TestResult has no addUnexpectedSuccess method, "
"reporting as failures",
RuntimeWarning)
result.addFailure(self, sys.exc_info())
except SkipTest as e:
@ -402,10 +417,9 @@ class CommonTestCase(unittest.TestCase):
@classmethod
def match(cls, filename):
"""
Determines if the specified filename should be handled by this
test class; this is done by looking for a match for the filename
using cls.match_re.
"""Determine if the specified filename should be handled by this test class.
This is done by looking for a match for the filename using cls.match_re.
"""
if not cls.match_re:
return False
@ -414,9 +428,7 @@ class CommonTestCase(unittest.TestCase):
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, testvars):
"""
Adds all the tests in the specified file to the specified suite.
"""
"""Add all the tests in the specified file to the specified suite."""
raise NotImplementedError
@property
@ -480,30 +492,30 @@ class CommonTestCase(unittest.TestCase):
def setup_SpecialPowers_observer(self):
self.marionette.set_context("chrome")
self.marionette.execute_script("""
let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
Components.utils.import("resource://gre/modules/Preferences.jsm");
Preferences.set(SECURITY_PREF, true);
let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
Components.utils.import("resource://gre/modules/Preferences.jsm");
Preferences.set(SECURITY_PREF, true);
if (!testUtils.hasOwnProperty("specialPowersObserver")) {
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
testUtils);
testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
testUtils.specialPowersObserver.init();
}
""")
if (!testUtils.hasOwnProperty("specialPowersObserver")) {
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
testUtils);
testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
testUtils.specialPowersObserver.init();
}
""")
def run_js_test(self, filename, marionette=None):
'''
Run a JavaScript test file and collect its set of assertions
into the current test's results.
"""Run a JavaScript test file.
It collects its set of assertions into the current test's results.
:param filename: The path to the JavaScript test file to execute.
May be relative to the current script.
:param marionette: The Marionette object in which to execute the test.
Defaults to self.marionette.
'''
"""
marionette = marionette or self.marionette
if not os.path.isabs(filename):
# Find the caller's filename and make the path relative to that.
@ -518,11 +530,11 @@ class CommonTestCase(unittest.TestCase):
js = f.read()
args = []
head_js = JSTest.head_js_re.search(js);
head_js = JSTest.head_js_re.search(js)
if head_js:
head_js = head_js.group(3)
head = open(os.path.join(os.path.dirname(filename), head_js), 'r')
js = head.read() + js;
js = head.read() + js
context = JSTest.context_re.search(js)
if context:
@ -568,7 +580,7 @@ class CommonTestCase(unittest.TestCase):
filename=os.path.basename(filename)
)
self.assertTrue(not 'timeout' in filename,
self.assertTrue('timeout' not in filename,
'expected timeout not triggered')
if 'fail' in filename:
@ -577,17 +589,20 @@ class CommonTestCase(unittest.TestCase):
else:
for failure in results['failures']:
diag = "" if failure.get('diag') is None else failure['diag']
name = "got false, expected true" if failure.get('name') is None else failure['name']
name = ("got false, expected true" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'FAIL',
message=diag)
for failure in results['expectedFailures']:
diag = "" if failure.get('diag') is None else failure['diag']
name = "got false, expected false" if failure.get('name') is None else failure['name']
name = ("got false, expected false" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'FAIL',
expected='FAIL', message=diag)
for failure in results['unexpectedSuccesses']:
diag = "" if failure.get('diag') is None else failure['diag']
name = "got true, expected false" if failure.get('name') is None else failure['name']
name = ("got true, expected false" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'PASS',
expected='FAIL', message=diag)
self.assertEqual(0, len(results['failures']),
@ -597,10 +612,10 @@ class CommonTestCase(unittest.TestCase):
if len(results['expectedFailures']) > 0:
raise _ExpectedFailure((AssertionError, AssertionError(''), None))
self.assertTrue(results['passed']
+ len(results['failures'])
+ len(results['expectedFailures'])
+ len(results['unexpectedSuccesses']) > 0,
self.assertTrue(results['passed'] +
len(results['failures']) +
len(results['expectedFailures']) +
len(results['unexpectedSuccesses']) > 0,
'no tests run')
except ScriptTimeoutException:
@ -613,7 +628,6 @@ class CommonTestCase(unittest.TestCase):
self.marionette.test_name = original_test_name
class MarionetteTestCase(CommonTestCase):
match_re = re.compile(r"test_(.*)\.py$")
@ -629,7 +643,8 @@ class MarionetteTestCase(CommonTestCase):
CommonTestCase.__init__(self, methodName, **kwargs)
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, testvars, **kwargs):
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette,
testvars, **kwargs):
# since we use imp.load_source to load test modules, if a module
# is loaded with the same name as another one the module would just be
# reloaded.
@ -649,7 +664,7 @@ class MarionetteTestCase(CommonTestCase):
for name in dir(test_mod):
obj = getattr(test_mod, name)
if (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, unittest.TestCase)):
issubclass(obj, unittest.TestCase)):
testnames = testloader.getTestCaseNames(obj)
for testname in testnames:
suite.addTest(obj(weakref.ref(marionette),
@ -696,6 +711,7 @@ class MarionetteTestCase(CommonTestCase):
else:
raise TimeoutException("wait_for_condition timed out")
class MarionetteJSTestCase(CommonTestCase):
match_re = re.compile(r"test_(.*)\.js$")
@ -709,7 +725,8 @@ class MarionetteJSTestCase(CommonTestCase):
CommonTestCase.__init__(self, methodName)
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, testvars, **kwargs):
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette,
testvars, **kwargs):
suite.addTest(cls(weakref.ref(marionette), jsFile=filepath, **kwargs))
def runTest(self):

View File

@ -33,9 +33,9 @@ import httpd
here = os.path.abspath(os.path.dirname(__file__))
def update_mozinfo(path=None):
"""walk up directories to find mozinfo.json and update the info"""
def update_mozinfo(path=None):
"""Walk up directories to find mozinfo.json and update the info."""
path = path or here
dirs = set()
while path != os.path.expanduser('~'):
@ -58,6 +58,7 @@ class MarionetteTest(TestResult):
else:
return self.name
class MarionetteTestResult(StructuredTestResult, TestResultCollection):
resultClass = MarionetteTest
@ -67,7 +68,7 @@ class MarionetteTestResult(StructuredTestResult, TestResultCollection):
TestResultCollection.__init__(self, 'MarionetteTest')
self.passed = 0
self.testsRun = 0
self.result_modifiers = [] # used by mixins to modify the result
self.result_modifiers = [] # used by mixins to modify the result
StructuredTestResult.__init__(self, *args, **kwargs)
@property
@ -133,11 +134,12 @@ class MarionetteTestResult(StructuredTestResult, TestResultCollection):
test_class = None
t = self.resultClass(name=name, test_class=test_class,
time_start=test.start_time, result_expected=result_expected,
context=context, **kwargs)
time_start=test.start_time, result_expected=result_expected,
context=context, **kwargs)
# call any registered result modifiers
for modifier in self.result_modifiers:
result_expected, result_actual, output, context = modifier(t, result_expected, result_actual, output, context)
result_expected, result_actual, output, context = modifier(
t, result_expected, result_actual, output, context)
t.finish(result_actual,
time_end=time.time() if test.start_time else 0,
reason=relevant_line(output),
@ -145,11 +147,13 @@ class MarionetteTestResult(StructuredTestResult, TestResultCollection):
self.append(t)
def addError(self, test, err):
self.add_test_result(test, output=self._exc_info_to_string(err, test), result_actual='ERROR')
self.add_test_result(test, output=self._exc_info_to_string(err, test),
result_actual='ERROR')
super(MarionetteTestResult, self).addError(test, err)
def addFailure(self, test, err):
self.add_test_result(test, output=self._exc_info_to_string(err, test), result_actual='UNEXPECTED-FAIL')
self.add_test_result(test, output=self._exc_info_to_string(err, test),
result_actual='UNEXPECTED-FAIL')
super(MarionetteTestResult, self).addFailure(test, err)
def addSuccess(self, test):
@ -193,7 +197,7 @@ class MarionetteTestResult(StructuredTestResult, TestResultCollection):
skip_log = True
for line in testcase.loglines:
str_line = ' '.join(line)
if not 'TEST-END' in str_line and not 'TEST-START' in str_line:
if 'TEST-END' not in str_line and 'TEST-START' not in str_line:
skip_log = False
break
if skip_log:
@ -260,106 +264,111 @@ class BaseMarionetteArguments(ArgumentParser):
'When a directory is specified, '
'all test files in the directory will be run.')
self.add_argument('--binary',
help='path to gecko executable to launch before running the test')
help='path to gecko executable to launch before running the test')
self.add_argument('--address',
help='host:port of running Gecko instance to connect to')
help='host:port of running Gecko instance to connect to')
self.add_argument('--emulator',
action='store_true',
help='If no --address is given, then the harness will launch an emulator. (See Remote options group.) '
'If --address is given, then the harness assumes you are running an '
'emulator already, and will launch gecko app on that emulator.')
action='store_true',
help='If no --address is given, then the harness will launch an '
'emulator. (See Remote options group.) '
'If --address is given, then the harness assumes you are '
'running an emulator already, and will launch gecko app '
'on that emulator.')
self.add_argument('--app',
help='application to use. see marionette_driver.geckoinstance')
help='application to use. see marionette_driver.geckoinstance')
self.add_argument('--app-arg',
dest='app_args',
action='append',
default=[],
help='specify a command line argument to be passed onto the application')
dest='app_args',
action='append',
default=[],
help='specify a command line argument to be passed onto the application')
self.add_argument('--profile',
help='profile to use when launching the gecko process. If not passed, then a profile will be '
'constructed and used',
type=dir_path)
help='profile to use when launching the gecko process. If not passed, '
'then a profile will be constructed and used',
type=dir_path)
self.add_argument('--pref',
action='append',
dest='prefs_args',
help=(" A preference to set. Must be a key-value pair"
" separated by a ':'."))
action='append',
dest='prefs_args',
help="A preference to set. Must be a key-value pair separated by a ':'.")
self.add_argument('--preferences',
action='append',
dest='prefs_files',
help=("read preferences from a JSON or INI file. For"
" INI, use 'file.ini:section' to specify a"
" particular section."))
action='append',
dest='prefs_files',
help="read preferences from a JSON or INI file. For INI, use "
"'file.ini:section' to specify a particular section.")
self.add_argument('--addon',
action='append',
help="addon to install; repeat for multiple addons.")
action='append',
help="addon to install; repeat for multiple addons.")
self.add_argument('--repeat',
type=int,
default=0,
help='number of times to repeat the test(s)')
type=int,
default=0,
help='number of times to repeat the test(s)')
self.add_argument('--testvars',
action='append',
help='path to a json file with any test data required')
action='append',
help='path to a json file with any test data required')
self.add_argument('--symbols-path',
help='absolute path to directory containing breakpad symbols, or the url of a zip file containing symbols')
help='absolute path to directory containing breakpad symbols, or the '
'url of a zip file containing symbols')
self.add_argument('--timeout',
type=int,
help='if a --timeout value is given, it will set the default page load timeout, search timeout and script timeout to the given value. If not passed in, it will use the default values of 30000ms for page load, 0ms for search timeout and 10000ms for script timeout')
type=int,
help='if a --timeout value is given, it will set the default page load '
'timeout, search timeout and script timeout to the given value. '
'If not passed in, it will use the default values of 30000ms for '
'page load, 0ms for search timeout and 10000ms for script timeout')
self.add_argument('--startup-timeout',
type=int,
default=60,
help='the max number of seconds to wait for a Marionette connection after launching a binary')
type=int,
default=60,
help='the max number of seconds to wait for a Marionette connection '
'after launching a binary')
self.add_argument('--shuffle',
action='store_true',
default=False,
help='run tests in a random order')
action='store_true',
default=False,
help='run tests in a random order')
self.add_argument('--shuffle-seed',
type=int,
default=random.randint(0, sys.maxint),
help='Use given seed to shuffle tests')
type=int,
default=random.randint(0, sys.maxint),
help='Use given seed to shuffle tests')
self.add_argument('--total-chunks',
type=int,
help='how many chunks to split the tests up into')
type=int,
help='how many chunks to split the tests up into')
self.add_argument('--this-chunk',
type=int,
help='which chunk to run')
type=int,
help='which chunk to run')
self.add_argument('--sources',
help='path to sources.xml (Firefox OS only)')
help='path to sources.xml (Firefox OS only)')
self.add_argument('--server-root',
help='url to a webserver or path to a document root from which content '
'resources are served (default: {}).'.format(os.path.join(
os.path.dirname(here), 'www')))
help='url to a webserver or path to a document root from which content '
'resources are served (default: {}).'.format(os.path.join(
os.path.dirname(here), 'www')))
self.add_argument('--gecko-log',
help="Define the path to store log file. If the path is"
" a directory, the real log file will be created"
" given the format gecko-(timestamp).log. If it is"
" a file, if will be used directly. '-' may be passed"
" to write to stdout. Default: './gecko.log'")
help="Define the path to store log file. If the path is"
" a directory, the real log file will be created"
" given the format gecko-(timestamp).log. If it is"
" a file, if will be used directly. '-' may be passed"
" to write to stdout. Default: './gecko.log'")
self.add_argument('--logger-name',
default='Marionette-based Tests',
help='Define the name to associate with the logger used')
default='Marionette-based Tests',
help='Define the name to associate with the logger used')
self.add_argument('--jsdebugger',
action='store_true',
default=False,
help='Enable the jsdebugger for marionette javascript.')
action='store_true',
default=False,
help='Enable the jsdebugger for marionette javascript.')
self.add_argument('--pydebugger',
help='Enable python post-mortem debugger when a test fails.'
' Pass in the debugger you want to use, eg pdb or ipdb.')
help='Enable python post-mortem debugger when a test fails.'
' Pass in the debugger you want to use, eg pdb or ipdb.')
self.add_argument('--socket-timeout',
type=float,
default=self.socket_timeout_default,
help='Set the global timeout for marionette socket operations.')
type=float,
default=self.socket_timeout_default,
help='Set the global timeout for marionette socket operations.')
self.add_argument('--disable-e10s',
action='store_false',
dest='e10s',
default=True,
help='Disable e10s when running marionette tests.')
action='store_false',
dest='e10s',
default=True,
help='Disable e10s when running marionette tests.')
self.add_argument('--tag',
action='append', dest='test_tags',
default=None,
help="Filter out tests that don't have the given tag. Can be "
"used multiple times in which case the test must contain "
"at least one of the given tags.")
action='append', dest='test_tags',
default=None,
help="Filter out tests that don't have the given tag. Can be "
"used multiple times in which case the test must contain "
"at least one of the given tags.")
self.add_argument('--workspace',
action='store',
default=None,
@ -367,9 +376,9 @@ class BaseMarionetteArguments(ArgumentParser):
"(Default: .) (Default profile dest: TMP)",
type=dir_path)
self.add_argument('-v', '--verbose',
action='count',
help='Increase verbosity to include debug messages with -v, '
'and trace messages with -vv.')
action='count',
help='Increase verbosity to include debug messages with -v, '
'and trace messages with -vv.')
self.register_argument_container(RemoteMarionetteArguments())
def register_argument_container(self, container):
@ -388,9 +397,7 @@ class BaseMarionetteArguments(ArgumentParser):
return (args, remainder)
def _get_preferences(self, prefs_files, prefs_args):
"""
return user defined profile preferences as a dict
"""
"""Return user defined profile preferences as a dict."""
# object that will hold the preferences
prefs = mozprofile.prefs.Preferences()
@ -458,6 +465,7 @@ class BaseMarionetteArguments(ArgumentParser):
return args
class RemoteMarionetteArguments(object):
name = 'Remote (Emulator/Device)'
args = [
@ -488,6 +496,7 @@ class RemoteMarionetteArguments(object):
]
class BaseMarionetteTestRunner(object):
textrunnerclass = MarionetteTextTestRunner
@ -498,7 +507,8 @@ class BaseMarionetteTestRunner(object):
logger=None, logdir=None,
repeat=0, testvars=None,
symbols_path=None, timeout=None,
shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1, total_chunks=1, sources=None,
shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1,
total_chunks=1, sources=None,
server_root=None, gecko_log=None, result_callbacks=None,
prefs=None, test_tags=None,
socket_timeout=BaseMarionetteArguments.socket_timeout_default,
@ -549,7 +559,8 @@ class BaseMarionetteTestRunner(object):
rv = {}
marionette = test._marionette_weakref()
# In the event we're gathering debug without starting a session, skip marionette commands
# In the event we're gathering debug without starting a session,
# skip marionette commands
if marionette.session is not None:
try:
with marionette.using_context(marionette.CONTEXT_CHROME):
@ -584,7 +595,8 @@ class BaseMarionetteTestRunner(object):
@property
def filename_pattern(self):
if self._filename_pattern is None:
self._filename_pattern = re.compile("^test(((_.+?)+?\.((py)|(js)))|(([A-Z].*?)+?\.js))$")
self._filename_pattern = re.compile(
"^test(((_.+?)+?\.((py)|(js)))|(([A-Z].*?)+?\.js))$")
return self._filename_pattern
@ -596,7 +608,7 @@ class BaseMarionetteTestRunner(object):
self._testvars = {}
def update(d, u):
""" Update a dictionary that may contain nested dictionaries. """
"""Update a dictionary that may contain nested dictionaries."""
for k, v in u.iteritems():
o = d.get(k, {})
if isinstance(v, dict) and isinstance(o, dict):
@ -667,8 +679,7 @@ class BaseMarionetteTestRunner(object):
@bin.setter
def bin(self, path):
"""
Set binary and reset parts of runner accordingly
"""Set binary and reset parts of runner accordingly.
Intended use: to change binary between calls to run_tests
"""
@ -713,7 +724,7 @@ class BaseMarionetteTestRunner(object):
if self.bin:
kwargs.update({
'bin': self.bin,
})
})
if self.emulator:
kwargs.update({
@ -736,9 +747,9 @@ class BaseMarionetteTestRunner(object):
})
if not self.bin and not self.emulator:
try:
#establish a socket connection so we can vertify the data come back
connection = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connection.connect((host,int(port)))
# Establish a socket connection so we can vertify the data come back
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection.connect((host, int(port)))
connection.close()
except Exception as e:
exc, val, tb = sys.exc_info()
@ -754,7 +765,8 @@ class BaseMarionetteTestRunner(object):
self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
result = self.marionette.execute_async_script("""
if((navigator.mozSettings == undefined) || (navigator.mozSettings == null) || (navigator.mozApps == undefined) || (navigator.mozApps == null)) {
if((navigator.mozSettings == undefined) || (navigator.mozSettings == null) ||
(navigator.mozApps == undefined) || (navigator.mozApps == null)) {
marionetteScriptFinished(false);
return;
}
@ -837,7 +849,8 @@ setReq.onerror = function() {
for test in tests:
self.add_test(test)
invalid_tests = [t['filepath'] for t in self.tests if not self._is_filename_valid(t['filepath'])]
invalid_tests = [t['filepath'] for t in self.tests
if not self._is_filename_valid(t['filepath'])]
if invalid_tests:
raise Exception("Test file names must be of the form "
"'test_something.py', 'test_something.js', or 'testSomething.js'."
@ -873,7 +886,7 @@ setReq.onerror = function() {
except Exception:
self.logger.warning('Could not get device info.')
#TODO Get version_info in Fennec case
# TODO: Get version_info in Fennec case
version_info = None
if self.bin:
version_info = mozversion.get_version(binary=self.bin,
@ -890,7 +903,7 @@ setReq.onerror = function() {
interrupted = None
try:
counter = self.repeat
while counter >=0:
while counter >= 0:
round_num = self.repeat - counter
if round_num > 0:
self.logger.info('\nREPEAT %d\n-------' % round_num)
@ -936,7 +949,8 @@ setReq.onerror = function() {
if self.unexpected_successes == 0:
self.logger.info('failed: %d' % self.failed)
else:
self.logger.info('failed: %d (unexpected sucesses: %d)' % (self.failed, self.unexpected_successes))
self.logger.info('failed: %d (unexpected sucesses: %d)' %
(self.failed, self.unexpected_successes))
if self.skipped == 0:
self.logger.info('todo: %d' % self.todo)
else:
@ -949,7 +963,7 @@ setReq.onerror = function() {
def start_httpd(self, need_external_ip):
warnings.warn("start_httpd has been deprecated in favour of create_httpd",
DeprecationWarning)
DeprecationWarning)
self.httpd = self.create_httpd(need_external_ip)
def create_httpd(self, need_external_ip):
@ -977,7 +991,6 @@ setReq.onerror = function() {
self.add_test(test_file)
return
file_ext = os.path.splitext(os.path.split(filepath)[-1])[1]
if file_ext == '.ini':
@ -987,7 +1000,7 @@ setReq.onerror = function() {
filters = []
if self.test_tags:
filters.append(tags(self.test_tags))
json_path = update_mozinfo(filepath)
update_mozinfo(filepath)
self.logger.info("mozinfo updated with the following: {}".format(None))
self.logger.info("mozinfo is: {}".format(mozinfo.info))
manifest_tests = manifest.active_tests(exists=False,
@ -997,9 +1010,9 @@ setReq.onerror = function() {
e10s=self.e10s,
**mozinfo.info)
if len(manifest_tests) == 0:
self.logger.error("no tests to run using specified "
self.logger.error("No tests to run using specified "
"combination of filters: {}".format(
manifest.fmt_filters()))
manifest.fmt_filters()))
target_tests = []
for test in manifest_tests:
@ -1018,7 +1031,8 @@ setReq.onerror = function() {
self.add_test(i["path"], i["expected"], test_container)
return
self.tests.append({'filepath': filepath, 'expected': expected, 'test_container': test_container})
self.tests.append({'filepath': filepath, 'expected': expected,
'test_container': test_container})
def run_test(self, filepath, expected, test_container):
@ -1056,12 +1070,14 @@ setReq.onerror = function() {
self.todo += len(results.skipped)
self.passed += results.passed
for failure in results.failures + results.errors:
self.failures.append((results.getInfo(failure), failure.output, 'TEST-UNEXPECTED-FAIL'))
self.failures.append((results.getInfo(failure), failure.output,
'TEST-UNEXPECTED-FAIL'))
if hasattr(results, 'unexpectedSuccesses'):
self.failed += len(results.unexpectedSuccesses)
self.unexpected_successes += len(results.unexpectedSuccesses)
for failure in results.unexpectedSuccesses:
self.failures.append((results.getInfo(failure), failure.output, 'TEST-UNEXPECTED-PASS'))
self.failures.append((results.getInfo(failure), failure.output,
'TEST-UNEXPECTED-PASS'))
if hasattr(results, 'expectedFailures'):
self.todo += len(results.expectedFailures)

View File

@ -39,14 +39,15 @@ class MarionetteHarness(object):
self.args = args or self.parse_args()
def parse_args(self, logger_defaults=None):
parser = self._parser_class(usage='%(prog)s [options] test_file_or_dir <test_file_or_dir> ...')
parser = self._parser_class(
usage='%(prog)s [options] test_file_or_dir <test_file_or_dir> ...')
parser.add_argument('--version', action='version',
help="Show version information.",
version="%(prog)s {version}"
" (using marionette-driver: {driver_version}, ".format(
version=__version__,
driver_version=driver_version
))
help="Show version information.",
version="%(prog)s {version}"
" (using marionette-driver: {driver_version}, ".format(
version=__version__,
driver_version=driver_version
))
mozlog.commandline.add_logging_group(parser)
args = parser.parse_args()
parser.verify_usage(args)

View File

@ -24,9 +24,8 @@ def run_marionette(context, **kwargs):
args = argparse.Namespace(**kwargs)
if not args.binary:
args.binary = context.find_firefox()
args.binary = args.binary or context.firefox_bin
args.e10s = context.mozharness_config.get('e10s', args.e10s)
test_root = os.path.join(context.package_root, 'marionette', 'tests')
if not args.tests:

View File

@ -13,31 +13,62 @@ from mach.decorators import (
Command,
)
here = os.path.abspath(os.path.dirname(__file__))
parser = None
def run_mochitest(context, **kwargs):
args = Namespace(**kwargs)
args.e10s = context.mozharness_config.get('e10s', args.e10s)
args.certPath = context.certs_dir
args.utilityPath = context.bin_dir
args.extraProfileFiles.append(os.path.join(context.bin_dir, 'plugins'))
if not args.app:
args.app = context.find_firefox()
if args.test_paths:
test_root = os.path.join(context.package_root, 'mochitest', 'tests')
normalize = partial(context.normalize_test_path, test_root)
args.test_paths = map(normalize, args.test_paths)
import mozinfo
if mozinfo.info['buildapp'] == 'mobile/android':
return run_mochitest_android(context, args)
return run_mochitest_desktop(context, args)
def run_mochitest_desktop(context, args):
args.app = args.app or context.firefox_bin
args.utilityPath = context.bin_dir
args.extraProfileFiles.append(os.path.join(context.bin_dir, 'plugins'))
from runtests import run_test_harness
return run_test_harness(parser, args)
def run_mochitest_android(context, args):
args.app = args.app or 'org.mozilla.fennec'
args.extraProfileFiles.append(os.path.join(context.package_root, 'mochitest', 'fonts'))
args.utilityPath = context.hostutils
args.xrePath = context.hostutils
config = context.mozharness_config
if config:
args.remoteWebServer = config['remote_webserver']
args.httpPort = config['emulator']['http_port']
args.sslPort = config['emulator']['ssl_port']
args.adbPath = config['exes']['adb'] % {'abs_work_dir': context.mozharness_workdir}
from runtestsremote import run_test_harness
return run_test_harness(parser, args)
def setup_argument_parser():
import mozinfo
mozinfo.find_and_update_from_json(os.path.dirname(here))
app = 'generic'
if mozinfo.info.get('buildapp') == 'mobile/android':
app = 'android'
from mochitest_options import MochitestArgumentParser
global parser
parser = MochitestArgumentParser(app='generic')
parser = MochitestArgumentParser(app=app)
return parser

View File

@ -994,6 +994,12 @@ class AndroidArguments(ArgumentContainer):
"help": "The transport to use for communication with the device [default: adb].",
"suppress": True,
}],
[["--adbpath"],
{"dest": "adbPath",
"default": None,
"help": "Path to adb binary.",
"suppress": True,
}],
[["--devicePort"],
{"dest": "devicePort",
"type": int,
@ -1065,29 +1071,22 @@ class AndroidArguments(ArgumentContainer):
if build_obj:
options.log_mach = '-'
device_args = {'deviceRoot': options.remoteTestRoot}
if options.dm_trans == "adb":
device_args['adbPath'] = options.adbPath
if options.deviceIP:
options.dm = DroidADB(
options.deviceIP,
options.devicePort,
deviceRoot=options.remoteTestRoot)
device_args['host'] = options.deviceIP
device_args['port'] = options.devicePort
elif options.deviceSerial:
options.dm = DroidADB(
None,
None,
deviceSerial=options.deviceSerial,
deviceRoot=options.remoteTestRoot)
else:
options.dm = DroidADB(deviceRoot=options.remoteTestRoot)
device_args['deviceSerial'] = options.deviceSerial
options.dm = DroidADB(**device_args)
elif options.dm_trans == 'sut':
if options.deviceIP is None:
parser.error(
"If --dm_trans = sut, you must provide a device IP")
options.dm = DroidSUT(
options.deviceIP,
options.devicePort,
deviceRoot=options.remoteTestRoot)
device_args['host'] = options.deviceIP
device_args['port'] = options.devicePort
options.dm = DroidSUT(**device_args)
if not options.remoteTestRoot:
options.remoteTestRoot = options.dm.deviceRoot

View File

@ -2398,34 +2398,32 @@ class MochitestDesktop(MochitestBase):
self.log.info("runtests.py | Running with e10s: {}".format(options.e10s))
self.log.info("runtests.py | Running tests: start.\n")
try:
status = self.runApp(testURL,
self.browserEnv,
options.app,
profile=self.profile,
extraArgs=options.browserArgs,
utilityPath=options.utilityPath,
debuggerInfo=debuggerInfo,
valgrindPath=valgrindPath,
valgrindArgs=valgrindArgs,
valgrindSuppFiles=valgrindSuppFiles,
symbolsPath=options.symbolsPath,
timeout=timeout,
detectShutdownLeaks=detectShutdownLeaks,
screenshotOnFail=options.screenshotOnFail,
bisectChunk=options.bisectChunk,
quiet=options.quiet,
marionette_args=marionette_args,
)
except KeyboardInterrupt:
self.log.info("runtests.py | Received keyboard interrupt.\n")
status = -1
except:
traceback.print_exc()
self.log.error(
"Automation Error: Received unexpected exception while running application\n")
status = 1
status = self.runApp(testURL,
self.browserEnv,
options.app,
profile=self.profile,
extraArgs=options.browserArgs,
utilityPath=options.utilityPath,
debuggerInfo=debuggerInfo,
valgrindPath=valgrindPath,
valgrindArgs=valgrindArgs,
valgrindSuppFiles=valgrindSuppFiles,
symbolsPath=options.symbolsPath,
timeout=timeout,
detectShutdownLeaks=detectShutdownLeaks,
screenshotOnFail=options.screenshotOnFail,
bisectChunk=options.bisectChunk,
quiet=options.quiet,
marionette_args=marionette_args,
)
except KeyboardInterrupt:
self.log.info("runtests.py | Received keyboard interrupt.\n")
status = -1
except:
traceback.print_exc()
self.log.error(
"Automation Error: Received unexpected exception while running application\n")
status = 1
finally:
self.stopServers()

View File

@ -1871,6 +1871,28 @@ function synthesizeQueryTextRect(aOffset, aLength, aWindow)
QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
}
/**
* Synthesize a query text rect array event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryTextRectArray(aOffset, aLength, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nullptr;
}
return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT_ARRAY,
aOffset, aLength, 0, 0,
QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
}
/**
* Synthesize a query editor rect event.
*

View File

@ -35,7 +35,7 @@ class DeviceManagerADB(DeviceManager):
connected = False
def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
adbPath='adb', deviceSerial=None, deviceRoot=None,
adbPath=None, deviceSerial=None, deviceRoot=None,
logLevel=logging.ERROR, autoconnect=True, runAdbAsRoot=False,
serverHost=None, serverPort=None, **kwargs):
DeviceManager.__init__(self, logLevel=logLevel,
@ -48,7 +48,7 @@ class DeviceManagerADB(DeviceManager):
self._serverPort = serverPort
# the path to adb, or 'adb' to assume that it's on the PATH
self._adbPath = adbPath
self._adbPath = adbPath or 'adb'
# The serial number of the device to use with adb, used in cases
# where multiple devices are being managed by the same adb instance.

View File

@ -43,7 +43,6 @@ config = {
'verify-emulator',
'install',
'run-tests',
'stop-emulator',
],
"emulator": {
"name": "test-1",

View File

@ -53,7 +53,6 @@ config = {
'verify-emulator',
'install',
'run-tests',
'stop-emulator',
],
"emulator": {
"name": "test-1",

View File

@ -38,7 +38,6 @@ config = {
'create-virtualenv',
'verify-emulator',
'run-tests',
'stop-emulator',
],
"emulator": {
"name": "test-1",

View File

@ -24,7 +24,7 @@ sys.path.insert(1, os.path.dirname(sys.path[0]))
from mozprocess import ProcessHandler
from mozharness.base.log import FATAL
from mozharness.base.script import BaseScript, PreScriptAction
from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
from mozharness.base.vcs.vcsbase import VCSMixin
from mozharness.mozilla.blob_upload import BlobUploadMixin, blobupload_config_options
from mozharness.mozilla.mozbase import MozbaseMixin
@ -85,7 +85,7 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
'verify-emulator',
'install',
'run-tests',
'stop-emulator'],
],
default_actions=['clobber',
'start-emulator',
'download-and-extract',
@ -93,7 +93,7 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
'verify-emulator',
'install',
'run-tests',
'stop-emulator'],
],
require_config_file=require_config_file,
config={
'virtualenv_modules': self.virtualenv_modules,
@ -724,7 +724,8 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
self._dump_emulator_log()
self.buildbot_status(tbpl_status, level=log_level)
def stop_emulator(self):
@PostScriptAction('run-tests')
def stop_emulator(self, action, success=None):
'''
Report emulator health, then make sure that the emulator has been stopped
'''
@ -737,7 +738,11 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
first (if the emulator is still running, logcat may still be running, which
may lock the blob upload directory, causing a hang).
'''
self._kill_processes(self.config["emulator_process_name"])
if self.config.get('blob_upload_branch'):
# Except on interactive workers, we want the emulator to keep running
# after the script is finished. So only kill it if blobber would otherwise
# have run anyway (it doesn't get run on interactive workers).
self._kill_processes(self.config["emulator_process_name"])
super(AndroidEmulatorTest, self).upload_blobber_files()
if __name__ == '__main__':

View File

@ -22,7 +22,7 @@ sys.path.insert(1, os.path.dirname(sys.path[0]))
from mozprocess import ProcessHandler
from mozharness.base.log import FATAL
from mozharness.base.script import BaseScript, PostScriptRun, PreScriptAction
from mozharness.base.script import BaseScript, PostScriptRun, PreScriptAction, PostScriptAction
from mozharness.base.vcs.vcsbase import VCSMixin
from mozharness.mozilla.blob_upload import BlobUploadMixin, blobupload_config_options
from mozharness.mozilla.mozbase import MozbaseMixin
@ -76,14 +76,14 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
'create-virtualenv',
'install',
'run-tests',
'stop-emulators'],
],
default_actions=['clobber',
'start-emulators',
'download-and-extract',
'create-virtualenv',
'install',
'run-tests',
'stop-emulators'],
],
require_config_file=require_config_file,
config={
'virtualenv_modules': self.virtualenv_modules,
@ -822,7 +822,8 @@ class AndroidEmulatorTest(BlobUploadMixin, TestingMixin, EmulatorMixin, VCSMixin
self.buildbot_status(joint_tbpl_status, level=joint_log_level)
def stop_emulators(self):
@PostScriptAction('run-tests')
def stop_emulators(self, action, success=None):
'''
Report emulator health, then make sure that every emulator has been stopped
'''

View File

@ -94,11 +94,11 @@ def find_firefox(context):
search_paths = []
# Check for a mozharness setup
if context.mozharness_config:
with open(context.mozharness_config, 'r') as f:
config = json.load(f)
workdir = os.path.join(config['base_work_dir'], config['work_dir'])
search_paths.append(os.path.join(workdir, 'application'))
config = context.mozharness_config
if config and 'binary_path' in config:
return config['binary_path']
elif config:
search_paths.append(os.path.join(context.mozharness_workdir, 'application'))
# Check for test-stage setup
dist_bin = os.path.join(os.path.dirname(context.package_root), 'bin')
@ -112,6 +112,15 @@ def find_firefox(context):
continue
def find_hostutils(context):
workdir = context.mozharness_workdir
hostutils = os.path.join(workdir, 'hostutils')
for fname in os.listdir(hostutils):
fpath = os.path.join(hostutils, fname)
if os.path.isdir(fpath) and fname.startswith('host-utils'):
return fpath
def normalize_test_path(test_root, path):
if os.path.isabs(path) or os.path.exists(path):
return os.path.normpath(os.path.abspath(path))
@ -137,25 +146,35 @@ def bootstrap(test_package_root):
import mach.main
def populate_context(context, key=None):
if key is not None:
if key is None:
context.package_root = test_package_root
context.bin_dir = os.path.join(test_package_root, 'bin')
context.certs_dir = os.path.join(test_package_root, 'certs')
context.module_dir = os.path.join(test_package_root, 'modules')
context.ancestors = ancestors
context.normalize_test_path = normalize_test_path
return
context.package_root = test_package_root
context.bin_dir = os.path.join(test_package_root, 'bin')
context.certs_dir = os.path.join(test_package_root, 'certs')
context.modules_dir = os.path.join(test_package_root, 'modules')
# The values for the following 'key's will be set lazily, and cached
# after first being invoked.
if key == 'firefox_bin':
return find_firefox(context)
context.ancestors = ancestors
context.find_firefox = types.MethodType(find_firefox, context)
context.normalize_test_path = normalize_test_path
if key == 'hostutils':
return find_hostutils(context)
# Search for a mozharness localconfig.json
context.mozharness_config = None
for dir_path in ancestors(test_package_root):
mozharness_config = os.path.join(dir_path, 'logs', 'localconfig.json')
if os.path.isfile(mozharness_config):
context.mozharness_config = mozharness_config
break
if key == 'mozharness_config':
for dir_path in ancestors(context.package_root):
mozharness_config = os.path.join(dir_path, 'logs', 'localconfig.json')
if os.path.isfile(mozharness_config):
with open(mozharness_config, 'rb') as f:
return json.load(f)
return {}
if key == 'mozharness_workdir':
config = context.mozharness_config
if config:
return os.path.join(config['base_work_dir'], config['work_dir'])
mach = mach.main.Mach(os.getcwd())
mach.populate_context_handler = populate_context

View File

@ -21,12 +21,11 @@ from mach.decorators import (
def run_xpcshell(context, **kwargs):
args = Namespace(**kwargs)
args.appPath = args.appPath or os.path.dirname(context.firefox_bin)
args.e10s = context.mozharness_config.get('e10s', args.e10s)
args.utility_path = context.bin_dir
args.testingModulesDir = context.modules_dir
if not args.appPath:
args.appPath = os.path.dirname(context.find_firefox())
if not args.xpcshell:
args.xpcshell = os.path.join(args.appPath, 'xpcshell')

View File

@ -127,6 +127,9 @@ add_task(function* testNarrate() {
$(NarrateTestUtils.FORWARD).click();
} while ((yield promiseEvent).type == "paragraphstart");
// This is to make sure we are not actively scrolling when the tab closes.
content.scroll(0, 0);
yield ContentTaskUtils.waitForCondition(
() => !$(NarrateTestUtils.STOP), "transitioned to stopped state");
NarrateTestUtils.isStoppedState(content, ok);

View File

@ -9,5 +9,8 @@
// Commas at the end of the line not the start
"comma-style": 2,
// Don't allow unused local variables unless they match the pattern
"no-unused-vars": [2, {"args": "none", "vars": "local", "varsIgnorePattern": "^(ids|ignored|unused)$"}],
}
}

View File

@ -101,7 +101,6 @@ this.InsecurePasswordUtils = {
let uri = Services.io.newURI(aForm.rootElement.action || aForm.rootElement.baseURI,
null, null);
let principal = gScriptSecurityManager.getCodebasePrincipal(uri);
let host = uri.host;
if (uri.schemeIs("http")) {
isFormSubmitHTTP = true;

View File

@ -162,7 +162,6 @@ this.LoginImport.prototype = {
rows = yield connection.execute("SELECT * FROM moz_disabledHosts");
for (let row of rows) {
try {
let id = row.getResultByName("id");
let hostname = row.getResultByName("hostname");
this.store.data.disabledHosts.push(hostname);

View File

@ -1189,7 +1189,7 @@ var LoginManagerContent = {
formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
})[0];
let [usernameField, newPasswordField, oldPasswordField] =
let [usernameField, newPasswordField] =
this._getFormFields(form, false, recipes);
// If we are not verifying a password field, we want

View File

@ -191,7 +191,6 @@ var LoginRecipesContent = {
*/
_filterRecipesForForm(aRecipes, aForm) {
let formDocURL = aForm.ownerDocument.location;
let host = formDocURL.host;
let hostRecipes = aRecipes;
let recipes = new Set();
log.debug("_filterRecipesForForm", aRecipes);

View File

@ -931,7 +931,6 @@ LoginManagerPrompter.prototype = {
let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
let togglePasswordLabel = this._getLocalizedString("togglePasswordLabel");
let togglePasswordAccessKey = this._getLocalizedString("togglePasswordAccessKey");
let displayHost = this._getShortDisplayHost(login.hostname);
this._getPopupNote().show(
browser,
@ -1284,7 +1283,6 @@ LoginManagerPrompter.prototype = {
*/
promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
this.log("promptToChangePasswordWithUsernames with count:", count);
const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
var usernames = logins.map(l => l.username);
var dialogText = this._getLocalizedString("userSelectText");

View File

@ -284,8 +284,6 @@ this.LoginManagerStorage_json.prototype = {
}) {
this._store.ensureDataReady();
let conditions = [];
function match(aLogin) {
for (let field in matchData) {
let wantedValue = matchData[field];
@ -398,7 +396,6 @@ this.LoginManagerStorage_json.prototype = {
},
countLogins(hostname, formSubmitURL, httpRealm) {
let count = {};
let loginData = {
hostname: hostname,
formSubmitURL: formSubmitURL,

View File

@ -2,5 +2,8 @@
"extends": [
"../../../../testing/mochitest/mochitest.eslintrc",
"../../../../testing/mochitest/chrome.eslintrc"
]
],
"rules": {
"no-unused-vars": 0,
},
}

View File

@ -91,8 +91,6 @@ runChecksAfterCommonInit(() => startTest());
// Might as well test the converse, too (username in password field).
function startTest() {
var form, input;
is($_(1, "uname").value, "", "Checking for unfilled username 1");
is($_(1, "pword").value, "", "Checking for unfilled password 1");

View File

@ -181,7 +181,6 @@ add_task(function* test_import_downgraded()
{
let store = new LoginStore(getTempFile("test-import-downgraded.json").path);
let loginsSqlite = getTempFile("test-logins-downgraded.sqlite").path;
let loginList = TestData.loginList();
// Create and populate the SQLite database first.
let connection = yield Sqlite.openConnection({ path: loginsSqlite });

View File

@ -51,32 +51,13 @@ const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
/**
* Fetches a folder's children, ordered by their position within the folder.
* Children without a GUID will be assigned one.
*/
fetchChildGuids: Task.async(function* (parentGuid) {
PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(parentGuid);
let db = yield PlacesUtils.promiseDBConnection();
let children = yield fetchAllChildren(db, parentGuid);
let childGuids = [];
let guidsToSet = new Map();
for (let child of children) {
let guid = child.guid;
if (!PlacesUtils.isValidGuid(guid)) {
// Give the child a GUID if it doesn't have one. This shouldn't happen,
// but the old bookmarks engine code does this, so we'll match its
// behavior until we're sure this can be removed.
guid = yield generateGuid(db);
BookmarkSyncLog.warn(`fetchChildGuids: Assigning ${
guid} to item without GUID ${child.id}`);
guidsToSet.set(child.id, guid);
}
childGuids.push(guid);
}
if (guidsToSet.size > 0) {
yield setGuids(guidsToSet);
}
return childGuids;
return children.map(child => child.guid);
}),
/**
@ -152,45 +133,6 @@ const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
PlacesUtils.bookmarks.removeFolderChildren(folderId, SOURCE_SYNC);
}),
/**
* Ensures an item with the |itemId| has a GUID, assigning one if necessary.
* We should never have a bookmark without a GUID, but the old Sync bookmarks
* engine code does this, so we'll match its behavior until we're sure it's
* not needed.
*
* This method can be removed and replaced with `PlacesUtils.promiseItemGuid`
* once bug 1294291 lands.
*
* @return {Promise} resolved once the GUID has been updated.
* @resolves to the existing or new GUID.
* @rejects if the item does not exist.
*/
ensureGuidForId: Task.async(function* (itemId) {
let guid;
try {
// Use the existing GUID if it exists. `promiseItemGuid` caches the GUID
// as a side effect, and throws if it's invalid.
guid = yield PlacesUtils.promiseItemGuid(itemId);
} catch (ex) {
BookmarkSyncLog.warn(`ensureGuidForId: Error fetching GUID for ${
itemId}`, ex);
if (!isInvalidCachedGuidError(ex)) {
throw ex;
}
// Give the item a GUID if it doesn't have one.
guid = yield PlacesUtils.withConnectionWrapper(
"BookmarkSyncUtils: ensureGuidForId", Task.async(function* (db) {
let guid = yield generateGuid(db);
BookmarkSyncLog.warn(`ensureGuidForId: Assigning ${
guid} to item without GUID ${itemId}`);
return setGuid(db, itemId, guid);
})
);
}
return guid;
}),
/**
* Changes the GUID of an existing item.
*
@ -200,13 +142,20 @@ const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
*/
changeGuid: Task.async(function* (oldGuid, newGuid) {
PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(oldGuid);
PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(newGuid);
let itemId = yield PlacesUtils.promiseItemId(oldGuid);
if (PlacesUtils.isRootItem(itemId)) {
throw new Error(`Cannot change GUID of Places root ${oldGuid}`);
}
return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: changeGuid",
db => setGuid(db, itemId, newGuid));
Task.async(function* (db) {
yield db.executeCached(`UPDATE moz_bookmarks SET guid = :newGuid
WHERE id = :itemId`, { newGuid, itemId });
PlacesUtils.invalidateCachedGuidFor(itemId);
return newGuid;
})
);
}),
/**
@ -350,11 +299,6 @@ function notify(observers, notification, args) {
}
}
function isInvalidCachedGuidError(error) {
return error && error.message ==
"Trying to update the GUIDs cache with an invalid GUID";
}
// A helper for whenever we want to know if a GUID doesn't exist in the places
// database. Primarily used to detect orphans on incoming records.
var GUIDMissing = Task.async(function* (guid) {
@ -785,23 +729,6 @@ var updateBookmarkMetadata = Task.async(function* (itemId, oldItem, newItem, upd
return newItem;
});
function generateGuid(db) {
return db.executeCached("SELECT GENERATE_GUID() AS guid").then(rows =>
rows[0].getResultByName("guid"));
}
function setGuids(guids) {
return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: setGuids",
db => db.executeTransaction(function* () {
let promises = [];
for (let [itemId, newGuid] of guids) {
promises.push(setGuid(db, itemId, newGuid));
}
return Promise.all(promises);
})
);
}
var setGuid = Task.async(function* (db, itemId, newGuid) {
yield db.executeCached(`UPDATE moz_bookmarks SET guid = :newGuid
WHERE id = :itemId`, { newGuid, itemId });

View File

@ -113,23 +113,6 @@ var populateTree = Task.async(function* populate(parentGuid, ...items) {
return guids;
});
function* insertWithoutGuid(info) {
let item = yield PlacesUtils.bookmarks.insert(info);
let id = yield PlacesUtils.promiseItemId(item.guid);
// All Places methods ensure we specify a valid GUID, so we insert
// an item and remove its GUID by modifying the DB directly.
yield PlacesUtils.withConnectionWrapper(
"test_sync_utils: insertWithoutGuid", db => db.executeCached(
`UPDATE moz_bookmarks SET guid = NULL WHERE guid = :guid`,
{ guid: item.guid }
)
);
PlacesUtils.invalidateCachedGuidFor(id);
return { id, item };
}
add_task(function* test_order() {
do_print("Insert some bookmarks");
let guids = yield populateTree(PlacesUtils.bookmarks.menuGuid, {
@ -183,84 +166,6 @@ add_task(function* test_order() {
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_fetchChildGuids_ensure_guids() {
let firstWithGuid = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "https://mozilla.org",
});
let { item: secondWithoutGuid } = yield* insertWithoutGuid({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "https://example.com",
});
let thirdWithGuid = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
});
do_print("Children without a GUID should be assigned one");
let childGuids = yield PlacesSyncUtils.bookmarks.fetchChildGuids(
PlacesUtils.bookmarks.menuGuid);
equal(childGuids.length, 3, "Should include all children");
equal(childGuids[0], firstWithGuid.guid,
"Should include first child GUID");
notEqual(childGuids[1], secondWithoutGuid.guid,
"Should assign new GUID to second child");
equal(childGuids[2], thirdWithGuid.guid,
"Should include third child GUID");
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_ensureGuidForId_invalid() {
yield rejects(PlacesSyncUtils.bookmarks.ensureGuidForId(-1),
"Should reject invalid item IDs");
let item = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "https://mozilla.org",
});
let id = yield PlacesUtils.promiseItemId(item.guid);
yield PlacesUtils.bookmarks.remove(item);
yield rejects(PlacesSyncUtils.bookmarks.ensureGuidForId(id),
"Should reject nonexistent item IDs");
});
add_task(function* test_ensureGuidForId() {
do_print("Item with GUID");
{
let item = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "https://mozilla.org",
});
let id = yield PlacesUtils.promiseItemId(item.guid);
let guid = yield PlacesSyncUtils.bookmarks.ensureGuidForId(id);
equal(guid, item.guid, "Should return GUID if one exists");
}
do_print("Item without GUID");
{
let { id, item } = yield* insertWithoutGuid({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "https://example.com",
});
let guid = yield PlacesSyncUtils.bookmarks.ensureGuidForId(id);
notEqual(guid, item.guid, "Should assign new GUID to item without one");
equal(yield PlacesUtils.promiseItemGuid(id), guid,
"Should map ID to new GUID");
equal(yield PlacesUtils.promiseItemId(guid), id,
"Should map new GUID to ID");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_changeGuid_invalid() {
yield rejects(PlacesSyncUtils.bookmarks.changeGuid(makeGuid()),
"Should require a new GUID");

View File

@ -20,6 +20,8 @@ skip-if = !e10s
[browser_bug982298.js]
[browser_bug1198465.js]
[browser_contentTitle.js]
[browser_crash_previous_frameloader.js]
run-if = e10s && crashreporter
[browser_default_image_filename.js]
[browser_f7_caret_browsing.js]
[browser_findbar.js]

View File

@ -0,0 +1,114 @@
"use strict";
/**
* Cleans up the .dmp and .extra file from a crash.
*
* @param subject (nsISupports)
* The subject passed through the ipc:content-shutdown
* observer notification when a content process crash has
* occurred.
*/
function cleanUpMinidump(subject) {
Assert.ok(subject instanceof Ci.nsIPropertyBag2,
"Subject needs to be a nsIPropertyBag2 to clean up properly");
let dumpID = subject.getPropertyAsAString("dumpID");
Assert.ok(dumpID, "There should be a dumpID");
if (dumpID) {
let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
dir.append("minidumps");
let file = dir.clone();
file.append(dumpID + ".dmp");
file.remove(true);
file = dir.clone();
file.append(dumpID + ".extra");
file.remove(true);
}
}
/**
* This test ensures that if a remote frameloader crashes after
* the frameloader owner swaps it out for a new frameloader,
* that a oop-browser-crashed event is not sent to the new
* frameloader's browser element.
*/
add_task(function* test_crash_in_previous_frameloader() {
// On debug builds, crashing tabs results in much thinking, which
// slows down the test and results in intermittent test timeouts,
// so we'll pump up the expected timeout for this test.
requestLongerTimeout(2);
if (!gMultiProcessBrowser) {
Assert.ok(false, "This test should only be run in multi-process mode.");
return;
}
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "http://example.com",
}, function*(browser) {
// First, sanity check...
Assert.ok(browser.isRemoteBrowser,
"This browser needs to be remote if this test is going to " +
"work properly.");
// We will wait for the oop-browser-crashed event to have
// a chance to appear. That event is fired when TabParents
// are destroyed, and that occurs _before_ ContentParents
// are destroyed, so we'll wait on the ipc:content-shutdown
// observer notification, which is fired when a ContentParent
// goes away. After we see this notification, oop-browser-crashed
// events should have fired.
let contentProcessGone = TestUtils.topicObserved("ipc:content-shutdown");
let sawTabCrashed = false;
let onTabCrashed = () => {
sawTabCrashed = true;
};
browser.addEventListener("oop-browser-crashed", onTabCrashed);
// The name of the game is to cause a crash in a remote browser,
// and then immediately swap out the browser for a non-remote one.
yield ContentTask.spawn(browser, null, function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
let dies = function() {
privateNoteIntentionalCrash();
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
badptr.contents
};
// Use a timeout to give the parent a little extra time
// to flip the remoteness of the browser. This has the
// potential to be a bit race-y, since in theory, the
// setTimeout could complete before the parent finishes
// the remoteness flip, which would mean we'd get the
// oop-browser-crashed event, and we'll fail here.
// Unfortunately, I can't find a way around that right
// now, since you cannot send a frameloader a message
// once its been replaced.
setTimeout(() => {
dump("\nEt tu, Brute?\n");
dies();
}, 0);
});
gBrowser.updateBrowserRemoteness(browser, false);
info("Waiting for content process to go away.");
let [subject, data] = yield contentProcessGone;
// If we don't clean up the minidump, the harness will
// complain.
cleanUpMinidump(subject);
info("Content process is gone!");
Assert.ok(!sawTabCrashed,
"Should not have seen the oop-browser-crashed event.");
browser.removeEventListener("oop-browser-crashed", onTabCrashed);
});
});

View File

@ -131,6 +131,7 @@ LINTER = {
'taskcluster',
'testing/firefox-ui',
'testing/marionette/client',
'testing/marionette/harness',
'testing/puppeteer',
'testing/talos/',
'tools/lint',

View File

@ -253,6 +253,27 @@ function checkRect(aRect, aExpectedRect, aMessage)
aRect.height == aExpectedRect.height;
}
function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
{
for (var i = 1; i < aExpectedTextRectArray.length; ++i) {
var rect = { left: {}, top: {}, width: {}, height: {} };
try {
aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
} catch (e) {
ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
return false;
}
function toRect(aRect)
{
return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
}
if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
return false;
}
}
return true;
}
function checkRectContainsRect(aRect, aContainer, aMessage)
{
var container = { left: Math.ceil(aContainer.left),
@ -2282,6 +2303,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
is(c.height, b.height, description + "'b' and 'c' should be same height");
// "abc" as array
var abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
!checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
return;
}
// 2nd <p> (can be computed with the rect of 'c')
var p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
@ -2292,10 +2320,17 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd <p> should be at similar to right of 'c'");
is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");
// 2nd <p> as array
var p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
!checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
if (!checkQueryContentResult(p2_2, description + "rect for \n of \r\n caused by 2nd <p>")) {
if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
return;
}
@ -2303,6 +2338,13 @@ function runQueryTextRectInContentEditableTest()
is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
!checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <p> should match with each query text rect result")) {
return;
}
}
// "d"
@ -2335,6 +2377,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
is(f.height, e.height, description + "'e' and 'f' should be same height");
// "def" as array
var defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
!checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
return;
}
// next of "f" (can be computed with rect of 'f')
var next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
@ -2345,6 +2394,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
// next of "f" as array
var next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
!checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
return;
}
// too big offset for the editor
var tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2356,6 +2412,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
// too big offset for the editors as array
var tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
// \n 0 123 4 567 8 9
// \r\n 01 234 56 789 01 23
@ -2382,10 +2445,17 @@ function runQueryTextRectInContentEditableTest()
is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
// 3rd <p> as array
var p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
!checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2, description + "rect for \n of \r\n caused by 3rd <p>")) {
if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
return;
}
@ -2393,6 +2463,13 @@ function runQueryTextRectInContentEditableTest()
is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
!checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
return;
}
}
// <br> in 3rd <p>
@ -2405,10 +2482,17 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");
// <br> in 3rd <p> as array
var brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
!checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(br_2, description + "rect for \n of \r\n caused by <br> in 3rd <p>")) {
if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
return;
}
@ -2416,6 +2500,13 @@ function runQueryTextRectInContentEditableTest()
is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
!checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> in 3rd <p> should match with each query text rect result")) {
return;
}
}
// next of <br> in 3rd <p>
@ -2429,6 +2520,13 @@ function runQueryTextRectInContentEditableTest()
is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
is(next_br.width, br.width, description + "next of <br> and <br> should be same width");
// next of <br> in 3rd <p> as array
var next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
!checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of <br> in 3rd <p> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2440,6 +2538,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
// \n 0 123 4 567 8
// \r\n 01 234 56 789 0
@ -2457,7 +2562,7 @@ function runQueryTextRectInContentEditableTest()
is(f.height, e.height, description + "'e' and 'f' should be same height");
// 3rd <p> (can be computed with rect of 'f')
var p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
return;
}
@ -2466,10 +2571,17 @@ function runQueryTextRectInContentEditableTest()
is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
// 3rd <p> as array
p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
!checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2, description + "rect for \n of \r\n caused by 3rd <p>")) {
if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
return;
}
@ -2477,6 +2589,13 @@ function runQueryTextRectInContentEditableTest()
is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
!checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
return;
}
}
// next of 3rd <p>
@ -2489,6 +2608,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");
// next of 3rd <p> as array
var next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
!checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2500,6 +2626,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "abc<br>def";
// \n 0123 456
// \r\n 01234 567
@ -2532,6 +2665,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
is(c.height, b.height, description + "'b' and 'c' should be same height");
// "abc" as array
abcAsArray = synthesizeQueryTextRectArray(0, 3);
if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
!checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
return;
}
// <br> (can be computed with the rect of 'c')
br = synthesizeQueryTextRect(3, 1);
if (!checkQueryContentResult(br, description + "rect for <br>")) {
@ -2542,6 +2682,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by <br> should be at similar to right of 'c'");
is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");
// <br> as array
brAsArray = synthesizeQueryTextRectArray(3, 1);
if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
!checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var br_2 = synthesizeQueryTextRect(4, 1);
@ -2553,6 +2700,13 @@ function runQueryTextRectInContentEditableTest()
is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var br_2AsArray = synthesizeQueryTextRectArray(4, 1);
if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br>") ||
!checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> should match with each query text rect result")) {
return;
}
}
// "d"
@ -2585,6 +2739,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
is(f.height, e.height, description + "'e' and 'f' should be same height");
// "def" as array
defAsArray = synthesizeQueryTextRectArray(kLFLen + 3, 3);
if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
!checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
return;
}
// next of "f" (can be computed with rect of 'f')
next_f = synthesizeQueryTextRect(kLFLen + 6, 1);
if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
@ -2595,6 +2756,13 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
// next of "f" as array
next_fAsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
!checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2606,6 +2774,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
// Note that this case does not have an empty line at the end.
contenteditable.innerHTML = "abc<br>def<br>";
// \n 0123 4567
@ -2633,10 +2808,17 @@ function runQueryTextRectInContentEditableTest()
is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
isSimilarTo(br2.left, f.left + f.width, 2, description + "left of a line breaker caused by 2nd <br> should be similar to right of 'f'");
// 2nd <br> as array
var br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
!checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
if (!checkQueryContentResult(br2_2, description + "rect for \n of \r\n caused by 2nd <br>")) {
if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
return;
}
@ -2644,6 +2826,13 @@ function runQueryTextRectInContentEditableTest()
is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
!checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
return;
}
}
// next of 2nd <br>
@ -2657,6 +2846,13 @@ function runQueryTextRectInContentEditableTest()
is(next_br2.height, br2.height, description + "2nd <br> and next of 2nd <br> should be same height");
is(next_br2.width, br2.width, description + "2nd <br> and next of 2nd <br> should be same width");
// next of 2nd <br> as array
var next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd <br>") ||
!checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd <br> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2668,6 +2864,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.height, next_br2.height, description + "too big offset and next of 2nd <br> should be same height");
is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd <br> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "abc<br>def<br><br>";
// \n 0123 4567 8
// \r\n 01234 56789 01
@ -2694,10 +2897,17 @@ function runQueryTextRectInContentEditableTest()
is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
ok(f.left < br2.left, description + "left of a line breaker caused by 2nd <br> should be bigger than left of 'f', f.left=" + f.left + ", br2.left=" + br2.left);
// 2nd <br> as array
br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
!checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
if (!checkQueryContentResult(br2_2, description + "rect for \n of \r\n caused by 2nd <br>")) {
if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
return;
}
@ -2705,6 +2915,13 @@ function runQueryTextRectInContentEditableTest()
is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
!checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
return;
}
}
// 3rd <br>
@ -2717,10 +2934,17 @@ function runQueryTextRectInContentEditableTest()
isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd <br> should be at similar to left of 'd'");
isSimilarTo(br3.height, d.height, 2, description + "next of 3rd <br> and 'd' should be similar height");
// 3rd <br> as array
var br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
if (!checkQueryContentResult(br3AsArray, description + "3rd <br>'s line breaker as array") ||
!checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd <br> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
var br3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(br3_2, description + "rect for \n of \r\n caused by 3rd <br>")) {
if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd <br>")) {
return;
}
@ -2728,6 +2952,13 @@ function runQueryTextRectInContentEditableTest()
is(br3_2.left, br3.left, description + "'\\r' and '\\n' should be at same left");
is(br3_2.height, br3.height, description + "'\\r' and '\\n' should be same height");
is(br3_2.width, br3.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
!checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <br> should match with each query text rect result")) {
return;
}
}
// next of 3rd <br>
@ -2741,6 +2972,13 @@ function runQueryTextRectInContentEditableTest()
is(next_br3.height, br3.height, description + "3rd <br> and next of 3rd <br> should be same height");
is(next_br3.width, br3.width, description + "3rd <br> and next of 3rd <br> should be same width");
// next of 3rd <br> as array
var next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd <br>") ||
!checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd <br> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
@ -2751,6 +2989,13 @@ function runQueryTextRectInContentEditableTest()
is(tooBigOffset.left, next_br3.left, description + "too big offset and next of 3rd <br> should be at same left");
is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd <br> should be same height");
is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd <br> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
}
function runCharAtPointTest(aFocusedEditor, aTargetName)
@ -4252,41 +4497,95 @@ function runCSSTransformTest()
var editorRectTranslated = synthesizeQueryEditorRect();
if (!checkQueryContentResult(editorRectTranslated,
"runCSSTransformTest: editorRectTranslated") ||
"runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
!checkRect(editorRectTranslated, movedRect(editorRect, 10, 15),
"runCSSTransformTest: editorRectTranslated")) {
"runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
return;
}
var firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
if (!checkQueryContentResult(firstCharRectTranslated,
"runCSSTransformTest: firstCharRectTranslated") ||
"runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
!checkRect(firstCharRectTranslated, movedRect(firstCharRect, 10, 15),
"runCSSTransformTest: firstCharRectTranslated")) {
"runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
return;
}
var lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslated,
"runCSSTransformTest: lastCharRectTranslated") ||
"runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
!checkRect(lastCharRectTranslated, movedRect(lastCharRect, 10, 15),
"runCSSTransformTest: lastCharRectTranslated")) {
"runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
return;
}
var caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
if (!checkQueryContentResult(caretRectTranslated,
"runCSSTransformTest: caretRectTranslated") ||
"runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
!checkRect(caretRectTranslated, movedRect(caretRect, 10, 15),
"runCSSTransformTest: caretRectTranslated")) {
"runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
return;
}
var caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
"runCSSTransformTest: caretRectBeforeFirstCharTranslated") ||
"runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
!checkRect(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15),
"runCSSTransformTest: caretRectBeforeFirstCharTranslated")) {
"runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
return;
}
var firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
var lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
// XXX It's too difficult to check the result with scale and rotate...
// For now, let's check if query text rect and query text rect array returns same rect.
textarea.style.transform = "scale(1.5)";
firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
if (!checkQueryContentResult(firstCharRectTranslated,
"runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
return;
}
lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslated,
"runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
return;
}
firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
textarea.style.transform = "rotate(30deg)";
firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
if (!checkQueryContentResult(firstCharRectTranslated,
"runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
return;
}
lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslated,
"runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
return;
}
firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
!checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
return;
}
} finally {
textarea.style.transform = "";
}
@ -4305,6 +4604,11 @@ function runBug722639Test()
return;
}
ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
var firstLineAsArray = synthesizeQueryTextRectArray(0, 1);
if (!checkQueryContentResult(firstLineAsArray, "runBug722639Test: 1st line as array") ||
!checkRectArray(firstLineAsArray, [firstLine], "runBug722639Test: 1st line as array should match with text rect result")) {
return;
}
if (kLFLen > 1) {
var firstLineLF = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(firstLineLF,
@ -4315,6 +4619,11 @@ function runBug722639Test()
is(firstLineLF.left, firstLine.left, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
is(firstLineLF.height, firstLine.height, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
is(firstLineLF.width, firstLine.width, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
var firstLineLFAsArray = synthesizeQueryTextRectArray(1, 1);
if (!checkQueryContentResult(firstLineLFAsArray, "runBug722639Test: 1st line's \\n rect as array") ||
!checkRectArray(firstLineLFAsArray, [firstLineLF], "runBug722639Test: 1st line's rect as array should match with text rect result")) {
return;
}
}
var secondLine = synthesizeQueryTextRect(kLFLen, 1);
if (!checkQueryContentResult(secondLine,
@ -4322,6 +4631,11 @@ function runBug722639Test()
return;
}
ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
var secondLineAsArray = synthesizeQueryTextRectArray(kLFLen, 1);
if (!checkQueryContentResult(secondLineAsArray, "runBug722639Test: 2nd line as array") ||
!checkRectArray(secondLineAsArray, [secondLine], "runBug722639Test: 2nd line as array should match with text rect result")) {
return;
}
if (kLFLen > 1) {
var secondLineLF = synthesizeQueryTextRect(kLFLen + 1, 1);
if (!checkQueryContentResult(secondLineLF,
@ -4332,6 +4646,11 @@ function runBug722639Test()
is(secondLineLF.left, secondLine.left, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
is(secondLineLF.height, secondLine.height, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
is(secondLineLF.width, secondLine.width, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
var secondLineLFAsArray = synthesizeQueryTextRectArray(kLFLen + 1, 1);
if (!checkQueryContentResult(secondLineLFAsArray, "runBug722639Test: 2nd line's \\n rect as array") ||
!checkRectArray(secondLineLFAsArray, [secondLineLF], "runBug722639Test: 2nd line's rect as array should match with text rect result")) {
return;
}
}
var lineHeight = secondLine.top - firstLine.top;
ok(lineHeight > 0,
@ -4348,6 +4667,11 @@ function runBug722639Test()
return;
}
ok(true, "runBug722639Test: " + i + "th line, top=" + currentLine.top + ", left=" + currentLine.left);
var currentLineAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1), 1);
if (!checkQueryContentResult(currentLineAsArray, "runBug722639Test: " + i + "th line as array") ||
!checkRectArray(currentLineAsArray, [currentLine], "runBug722639Test: " + i + "th line as array should match with text rect result")) {
return;
}
// NOTE: the top position may be 1px larger or smaller than other lines
// due to sub pixel positioning.
if (Math.abs(currentLine.top - (previousTop + lineHeight)) <= 1) {
@ -4370,6 +4694,11 @@ function runBug722639Test()
is(currentLineLF.left, currentLine.left, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
is(currentLineLF.height, currentLine.height, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
is(currentLineLF.width, currentLine.width, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
var currentLineLFAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1) + 1, 1);
if (!checkQueryContentResult(currentLineLFAsArray, "runBug722639Test: " + i + "th line's \\n rect as array") ||
!checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
return;
}
}
previousTop = currentLine.top;
}