diff --git a/.gitignore b/.gitignore index 30a162aaff34..2f32d9bf78b2 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,6 @@ testing/talos/talos/tests/devtools/damp.manifest.develop # Ignore files created when running a reftest. lextab.py + +# tup database +/.tup diff --git a/.hgignore b/.hgignore index 5a503316ccfe..b258709c7a65 100644 --- a/.hgignore +++ b/.hgignore @@ -129,3 +129,6 @@ GPATH # Ignore files created when running a reftest. ^lextab.py$ + +# tup database +^\.tup diff --git a/Makefile.in b/Makefile.in index 04cfaf39ff5b..9a1dec40d09b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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. diff --git a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest index f4ab1fcabc74..ad5ea4b344fa 100644 --- a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest +++ b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest @@ -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", diff --git a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest index 3af441e89851..8992eda70044 100644 --- a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest +++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest @@ -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 diff --git a/browser/config/tooltool-manifests/linux32/releng.manifest b/browser/config/tooltool-manifests/linux32/releng.manifest index d6daa57c6bff..7363ba9be367 100644 --- a/browser/config/tooltool-manifests/linux32/releng.manifest +++ b/browser/config/tooltool-manifests/linux32/releng.manifest @@ -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 diff --git a/browser/config/tooltool-manifests/linux64/hazard.manifest b/browser/config/tooltool-manifests/linux64/hazard.manifest index f05dcf166c5b..b7c7d7fb57a0 100644 --- a/browser/config/tooltool-manifests/linux64/hazard.manifest +++ b/browser/config/tooltool-manifests/linux64/hazard.manifest @@ -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", diff --git a/browser/config/tooltool-manifests/linux64/releng.manifest b/browser/config/tooltool-manifests/linux64/releng.manifest index 8f00491796dd..9ca700151ff6 100644 --- a/browser/config/tooltool-manifests/linux64/releng.manifest +++ b/browser/config/tooltool-manifests/linux64/releng.manifest @@ -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 diff --git a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest index c90900beca5e..0ce5442a71e9 100644 --- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest +++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest @@ -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 } ] diff --git a/browser/config/tooltool-manifests/macosx64/releng.manifest b/browser/config/tooltool-manifests/macosx64/releng.manifest index 2dd433944b72..ddb845d691ca 100644 --- a/browser/config/tooltool-manifests/macosx64/releng.manifest +++ b/browser/config/tooltool-manifests/macosx64/releng.manifest @@ -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 diff --git a/browser/config/tooltool-manifests/win32/clang.manifest b/browser/config/tooltool-manifests/win32/clang.manifest index 5ae386cbc272..180876e9ba99 100644 --- a/browser/config/tooltool-manifests/win32/clang.manifest +++ b/browser/config/tooltool-manifests/win32/clang.manifest @@ -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 }, { diff --git a/browser/config/tooltool-manifests/win32/releng.manifest b/browser/config/tooltool-manifests/win32/releng.manifest index cf167dfb4139..43af53c507ef 100644 --- a/browser/config/tooltool-manifests/win32/releng.manifest +++ b/browser/config/tooltool-manifests/win32/releng.manifest @@ -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 diff --git a/browser/config/tooltool-manifests/win64/clang.manifest b/browser/config/tooltool-manifests/win64/clang.manifest index 0b8dea578ad1..2cfa78209182 100644 --- a/browser/config/tooltool-manifests/win64/clang.manifest +++ b/browser/config/tooltool-manifests/win64/clang.manifest @@ -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", diff --git a/browser/config/tooltool-manifests/win64/releng.manifest b/browser/config/tooltool-manifests/win64/releng.manifest index e111efa801fb..b6d3a6197bf5 100644 --- a/browser/config/tooltool-manifests/win64/releng.manifest +++ b/browser/config/tooltool-manifests/win64/releng.manifest @@ -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", diff --git a/build/autoconf/android.m4 b/build/autoconf/android.m4 index dc64ce070cb4..53a2e3b4868c 100644 --- a/build/autoconf/android.m4 +++ b/build/autoconf/android.m4 @@ -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]) diff --git a/devtools/client/debugger/new/index.html b/devtools/client/debugger/new/index.html index 4340a0e7a4f5..4eae02c0ab28 100644 --- a/devtools/client/debugger/new/index.html +++ b/devtools/client/debugger/new/index.html @@ -8,6 +8,9 @@
+ diff --git a/devtools/client/framework/toolbox-process-window.js b/devtools/client/framework/toolbox-process-window.js index be65c55bba02..25e1492505f3 100644 --- a/devtools/client/framework/toolbox-process-window.js +++ b/devtools/client/framework/toolbox-process-window.js @@ -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() { diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js index 2c1c392b0e65..f8cef4e92425 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js @@ -7,6 +7,11 @@ const HIGHLIGHTER_TYPE = "EyeDropper"; const ID = "eye-dropper-"; +const TEST_URI = ` + +eye-dropper test`; 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") { diff --git a/devtools/client/themes/rules.css b/devtools/client/themes/rules.css index 5d594e3db6e8..729d24232b37 100644 --- a/devtools/client/themes/rules.css +++ b/devtools/client/themes/rules.css @@ -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; diff --git a/devtools/client/webconsole/test/browser_webconsole_output_06.js b/devtools/client/webconsole/test/browser_webconsole_output_06.js index 04527bd5a118..90d0813490cf 100644 --- a/devtools/client/webconsole/test/browser_webconsole_output_06.js +++ b/devtools/client/webconsole/test/browser_webconsole_output_06.js @@ -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() { diff --git a/devtools/server/actors/highlighters/eye-dropper.js b/devtools/server/actors/highlighters/eye-dropper.js index cfeb0b264292..d75f10872251 100644 --- a/devtools/server/actors/highlighters/eye-dropper.js +++ b/devtools/server/actors/highlighters/eye-dropper.js @@ -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)); +} diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index 009b5fe77959..ed295896fbc2 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -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; } } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 51a5c0978cca..41b85f59fbc7 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -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 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); } diff --git a/dom/canvas/MurmurHash3.cpp b/dom/canvas/MurmurHash3.cpp index 269a22761ca3..87cf709341d1 100644 --- a/dom/canvas/MurmurHash3.cpp +++ b/dom/canvas/MurmurHash3.cpp @@ -8,6 +8,7 @@ // non-native version will be less than optimal. #include "MurmurHash3.h" +#include namespace { @@ -20,8 +21,6 @@ namespace { #define FORCE_INLINE __forceinline -#include - #define ROTL32(x,y) _rotl(x,y) #define ROTL64(x,y) _rotl64(x,y) diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index c06b2eeeed66..1bbb75f5b7d8 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -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 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 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())); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 42808b9715a4..811142f84f05 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -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 owner = do_QueryInterface(frameElement); + if (owner) { + RefPtr 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; diff --git a/dom/media/test/external/external_media_harness/testcase.py b/dom/media/test/external/external_media_harness/testcase.py index 03ec1a79ceac..f244bbe0eeec 100644 --- a/dom/media/test/external/external_media_harness/testcase.py +++ b/dom/media/test/external/external_media_harness/testcase.py @@ -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('.')] diff --git a/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py b/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py index 93dafdd55e44..62ebccef19b3 100644 --- a/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py +++ b/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py @@ -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 diff --git a/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py b/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py index a76cf815339c..fce2dfff988a 100644 --- a/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py +++ b/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py @@ -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: diff --git a/dom/media/test/external/external_media_tests/playback/test_eme_playback.py b/dom/media/test/external/external_media_tests/playback/test_eme_playback.py index e165dddd504f..9c7eb6725811 100644 --- a/dom/media/test/external/external_media_tests/playback/test_eme_playback.py +++ b/dom/media/test/external/external_media_tests/playback/test_eme_playback.py @@ -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): diff --git a/dom/media/test/external/external_media_tests/playback/youtube/test_basic_playback.py b/dom/media/test/external/external_media_tests/playback/youtube/test_basic_playback.py index 84bc146b866f..c479db55bada 100644 --- a/dom/media/test/external/external_media_tests/playback/youtube/test_basic_playback.py +++ b/dom/media/test/external/external_media_tests/playback/youtube/test_basic_playback.py @@ -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: diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.js b/dom/network/tests/test_tcpsocket_client_and_server_basics.js index 73d9df440e32..f47689b90324 100644 --- a/dom/network/tests/test_tcpsocket_client_and_server_basics.js +++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js @@ -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. diff --git a/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html index 1b4aad83c01a..f7e2024f6264 100644 --- a/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html +++ b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html @@ -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); } }; diff --git a/gfx/vr/VRDisplayHost.cpp b/gfx/vr/VRDisplayHost.cpp index 38504fdd2932..68746e4e413b 100644 --- a/gfx/vr/VRDisplayHost.cpp +++ b/gfx/vr/VRDisplayHost.cpp @@ -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) diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index df2f592c42ec..e5486ecd1fd2 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -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. diff --git a/layout/tools/reftest/mach_test_package_commands.py b/layout/tools/reftest/mach_test_package_commands.py index ec35637e4146..86b4a620e069 100644 --- a/layout/tools/reftest/mach_test_package_commands.py +++ b/layout/tools/reftest/mach_test_package_commands.py @@ -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')] diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index 8a4776606827..8de9175fc087 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -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}" diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index d6032458b9bb..d62ff543c7ec 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -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 ; diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java index e21e307ba4e8..f60d46fd126d 100644 --- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java @@ -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); diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java index 4df59e1dd75d..aad78498081d 100644 --- a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java +++ b/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java @@ -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 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(), + factory); + } /** * Submit the request for execution. diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java index c1572b7fd9dc..3ae9d15d0eb0 100644 --- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java +++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java @@ -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 visitedLinkSet = new HashSet<>(); + final HashSet 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; } diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java index 330d8d508e52..8e0018ce6e5d 100644 --- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java +++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java @@ -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. diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java index e9c3fad1a106..a12dad2ad424 100644 --- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java +++ b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java @@ -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)) { diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java index ded811721650..414beeaa053b 100644 --- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java +++ b/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java @@ -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)); } } diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java index b972db8ff193..4652345b4dd5 100644 --- a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java +++ b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java @@ -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); diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 587cc399ff61..1e5ce2cddad1 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -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'] diff --git a/mobile/android/base/resources/layout/activity_stream_topsites_card.xml b/mobile/android/base/resources/layout/activity_stream_topsites_card.xml index 10f95bbd8f21..d1e820c83841 100644 --- a/mobile/android/base/resources/layout/activity_stream_topsites_card.xml +++ b/mobile/android/base/resources/layout/activity_stream_topsites_card.xml @@ -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"> + tools:background="@drawable/favicon_globe" + gecko:enableRoundCorners="false"/> + diff --git a/mobile/android/config/tooltool-manifests/android/releng.manifest b/mobile/android/config/tooltool-manifests/android/releng.manifest index 7a0936ce968b..57d7b49588b0 100644 --- a/mobile/android/config/tooltool-manifests/android/releng.manifest +++ b/mobile/android/config/tooltool-manifests/android/releng.manifest @@ -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", diff --git a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestLegacyLoader.java b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestLegacyLoader.java index 1f678ba0d962..693a8203c934 100644 --- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestLegacyLoader.java +++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestLegacyLoader.java @@ -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 iterator = request.getIconIterator(); + if (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } } diff --git a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java index 6057c077627e..fef2d94838eb 100644 --- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java +++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java @@ -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() { diff --git a/moz.configure b/moz.configure index 3be8b9fc07db..0b0c752c9401 100644 --- a/moz.configure +++ b/moz.configure @@ -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) diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp index 390b8ea932b9..316847c97e8e 100644 --- a/netwerk/base/Predictor.cpp +++ b/netwerk/base/Predictor.cpp @@ -1632,7 +1632,7 @@ Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry, nsCOMPtr 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 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; diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp index 0a6ee8cf06d6..18dd25356815 100644 --- a/netwerk/protocol/http/Http2Compression.cpp +++ b/netwerk/protocol/http/Http2Compression.cpp @@ -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 diff --git a/netwerk/protocol/http/Http2Compression.h b/netwerk/protocol/http/Http2Compression.h index c9f0f61bbc60..074dd018e4bc 100644 --- a/netwerk/protocol/http/Http2Compression.h +++ b/netwerk/protocol/http/Http2Compression.h @@ -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 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; }; diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 086756c905b5..7033d4205c0f 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -123,7 +123,6 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t versio mInputFrameBuffer = MakeUnique(mInputFrameBufferSize); mOutputQueueBuffer = MakeUnique(mOutputQueueSize); mDecompressBuffer.SetCapacity(kDefaultBufferSize); - mDecompressor.SetCompressor(&mCompressor); mPushAllowance = gHttpHandler->SpdyPushAllowance(); mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance); diff --git a/python/mozbuild/mozbuild/backend/__init__.py b/python/mozbuild/mozbuild/backend/__init__.py index 331bc9fca86e..64bcb87d9625 100644 --- a/python/mozbuild/mozbuild/backend/__init__.py +++ b/python/mozbuild/mozbuild/backend/__init__.py @@ -10,6 +10,7 @@ backends = { 'FasterMake': 'mozbuild.backend.fastermake', 'FasterMake+RecursiveMake': None, 'RecursiveMake': 'mozbuild.backend.recursivemake', + 'Tup': 'mozbuild.backend.tup', 'VisualStudio': 'mozbuild.backend.visualstudio', } diff --git a/python/mozbuild/mozbuild/backend/base.py b/python/mozbuild/mozbuild/backend/base.py index b9c9c36168f4..f5e0c2d3c8c3 100644 --- a/python/mozbuild/mozbuild/backend/base.py +++ b/python/mozbuild/mozbuild/backend/base.py @@ -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. diff --git a/python/mozbuild/mozbuild/backend/tup.py b/python/mozbuild/mozbuild/backend/tup.py new file mode 100644 index 000000000000..a055a561d8a1 --- /dev/null +++ b/python/mozbuild/mozbuild/backend/tup.py @@ -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 diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index 47244004ba19..21ffde34286a 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -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)}, diff --git a/services/sync/modules/engines/bookmarks.js b/services/sync/modules/engines/bookmarks.js index 09e6b3ac0e8c..7a2c779b83dd 100644 --- a/services/sync/modules/engines/bookmarks.js +++ b/services/sync/modules/engines/bookmarks.js @@ -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 diff --git a/testing/marionette/harness/.flake8 b/testing/marionette/harness/.flake8 new file mode 100644 index 000000000000..b92bd858edd2 --- /dev/null +++ b/testing/marionette/harness/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 99 +exclude = __init__.py,disti/*,build/*,session/*,marionette/runner/mixins/*, marionette/tests/* diff --git a/testing/marionette/harness/docs/conf.py b/testing/marionette/harness/docs/conf.py index 0e418020127a..c3a74eef6183 100644 --- a/testing/marionette/harness/docs/conf.py +++ b/testing/marionette/harness/docs/conf.py @@ -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 # " v 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 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' diff --git a/testing/marionette/harness/marionette/marionette_test.py b/testing/marionette/harness/marionette/marionette_test.py index dce3e4820da6..2b31e9bc3f31 100644 --- a/testing/marionette/harness/marionette/marionette_test.py +++ b/testing/marionette/harness/marionette/marionette_test.py @@ -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): diff --git a/testing/marionette/harness/marionette/runner/base.py b/testing/marionette/harness/marionette/runner/base.py index 5c928e2f702b..d9bd4f19c443 100644 --- a/testing/marionette/harness/marionette/runner/base.py +++ b/testing/marionette/harness/marionette/runner/base.py @@ -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) diff --git a/testing/marionette/harness/marionette/runtests.py b/testing/marionette/harness/marionette/runtests.py index aadda3c54970..b009839f1908 100644 --- a/testing/marionette/harness/marionette/runtests.py +++ b/testing/marionette/harness/marionette/runtests.py @@ -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 ...') + parser = self._parser_class( + usage='%(prog)s [options] 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) diff --git a/testing/marionette/mach_test_package_commands.py b/testing/marionette/mach_test_package_commands.py index 6c24fc6a1b8f..4adccbd7b8d2 100644 --- a/testing/marionette/mach_test_package_commands.py +++ b/testing/marionette/mach_test_package_commands.py @@ -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: diff --git a/testing/mochitest/mach_test_package_commands.py b/testing/mochitest/mach_test_package_commands.py index 5c677e340c71..4c51f87c1e21 100644 --- a/testing/mochitest/mach_test_package_commands.py +++ b/testing/mochitest/mach_test_package_commands.py @@ -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 diff --git a/testing/mochitest/mochitest_options.py b/testing/mochitest/mochitest_options.py index d17c422e7613..fa24952b0879 100644 --- a/testing/mochitest/mochitest_options.py +++ b/testing/mochitest/mochitest_options.py @@ -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 diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index 6989efb726a3..142adfd84fbb 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -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() diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js index 6b8a6fa7982d..ee2565256937 100644 --- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -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. * diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py index f4cf68fa279c..c7609d582259 100644 --- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py +++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py @@ -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. diff --git a/testing/mozharness/configs/android/androidarm.py b/testing/mozharness/configs/android/androidarm.py index 54f5f6c59b56..fc4f742dcf39 100644 --- a/testing/mozharness/configs/android/androidarm.py +++ b/testing/mozharness/configs/android/androidarm.py @@ -43,7 +43,6 @@ config = { 'verify-emulator', 'install', 'run-tests', - 'stop-emulator', ], "emulator": { "name": "test-1", diff --git a/testing/mozharness/configs/android/androidarm_4_3.py b/testing/mozharness/configs/android/androidarm_4_3.py index 337eee22fb30..839288893ab6 100644 --- a/testing/mozharness/configs/android/androidarm_4_3.py +++ b/testing/mozharness/configs/android/androidarm_4_3.py @@ -53,7 +53,6 @@ config = { 'verify-emulator', 'install', 'run-tests', - 'stop-emulator', ], "emulator": { "name": "test-1", diff --git a/testing/mozharness/configs/android/androidx86-tc.py b/testing/mozharness/configs/android/androidx86-tc.py index 0258ad0c2d8b..8141b77f6fc4 100644 --- a/testing/mozharness/configs/android/androidx86-tc.py +++ b/testing/mozharness/configs/android/androidx86-tc.py @@ -38,7 +38,6 @@ config = { 'create-virtualenv', 'verify-emulator', 'run-tests', - 'stop-emulator', ], "emulator": { "name": "test-1", diff --git a/testing/mozharness/scripts/android_emulator_unittest.py b/testing/mozharness/scripts/android_emulator_unittest.py index 8defa3f9ade1..e64fdaa6230c 100644 --- a/testing/mozharness/scripts/android_emulator_unittest.py +++ b/testing/mozharness/scripts/android_emulator_unittest.py @@ -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__': diff --git a/testing/mozharness/scripts/androidx86_emulator_unittest.py b/testing/mozharness/scripts/androidx86_emulator_unittest.py index ebf38de6163b..a67baa101f05 100644 --- a/testing/mozharness/scripts/androidx86_emulator_unittest.py +++ b/testing/mozharness/scripts/androidx86_emulator_unittest.py @@ -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 ''' diff --git a/testing/tools/mach_test_package_bootstrap.py b/testing/tools/mach_test_package_bootstrap.py index 763a0efef7e8..f7a8fb397b3a 100644 --- a/testing/tools/mach_test_package_bootstrap.py +++ b/testing/tools/mach_test_package_bootstrap.py @@ -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 diff --git a/testing/xpcshell/mach_test_package_commands.py b/testing/xpcshell/mach_test_package_commands.py index 8056fdeacb61..fc7d27385937 100644 --- a/testing/xpcshell/mach_test_package_commands.py +++ b/testing/xpcshell/mach_test_package_commands.py @@ -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') diff --git a/toolkit/components/narrate/test/browser_narrate.js b/toolkit/components/narrate/test/browser_narrate.js index a568dd62ce58..a194af615b8a 100644 --- a/toolkit/components/narrate/test/browser_narrate.js +++ b/toolkit/components/narrate/test/browser_narrate.js @@ -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); diff --git a/toolkit/components/passwordmgr/.eslintrc b/toolkit/components/passwordmgr/.eslintrc index 85c37ef0877e..73bd7f81d379 100644 --- a/toolkit/components/passwordmgr/.eslintrc +++ b/toolkit/components/passwordmgr/.eslintrc @@ -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)$"}], } } diff --git a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm index 48141b9d0577..e415e8d4a483 100644 --- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm +++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm @@ -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; diff --git a/toolkit/components/passwordmgr/LoginImport.jsm b/toolkit/components/passwordmgr/LoginImport.jsm index 6b2574c90434..75db3892ca1d 100644 --- a/toolkit/components/passwordmgr/LoginImport.jsm +++ b/toolkit/components/passwordmgr/LoginImport.jsm @@ -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); diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 0e2afe28551e..8d0c8d83b9b9 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -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 diff --git a/toolkit/components/passwordmgr/LoginRecipes.jsm b/toolkit/components/passwordmgr/LoginRecipes.jsm index 029c93d53c59..0d15151fe4a8 100644 --- a/toolkit/components/passwordmgr/LoginRecipes.jsm +++ b/toolkit/components/passwordmgr/LoginRecipes.jsm @@ -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); diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 3b86a2206395..a0e09131489f 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -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"); diff --git a/toolkit/components/passwordmgr/storage-json.js b/toolkit/components/passwordmgr/storage-json.js index 95f42694e8b3..abcf46070c43 100644 --- a/toolkit/components/passwordmgr/storage-json.js +++ b/toolkit/components/passwordmgr/storage-json.js @@ -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, diff --git a/toolkit/components/passwordmgr/test/.eslintrc b/toolkit/components/passwordmgr/test/.eslintrc index 590eaa4aafee..c48874775b17 100644 --- a/toolkit/components/passwordmgr/test/.eslintrc +++ b/toolkit/components/passwordmgr/test/.eslintrc @@ -2,5 +2,8 @@ "extends": [ "../../../../testing/mochitest/mochitest.eslintrc", "../../../../testing/mochitest/chrome.eslintrc" - ] + ], + "rules": { + "no-unused-vars": 0, + }, } diff --git a/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html b/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html index a0ac8a5a386c..e107cebe6048 100644 --- a/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html +++ b/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html @@ -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"); diff --git a/toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js b/toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js index 4897baebacc9..2087355bed1f 100644 --- a/toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js +++ b/toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js @@ -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 }); diff --git a/toolkit/components/places/PlacesSyncUtils.jsm b/toolkit/components/places/PlacesSyncUtils.jsm index fe4bb94cabdf..3887870d9d61 100644 --- a/toolkit/components/places/PlacesSyncUtils.jsm +++ b/toolkit/components/places/PlacesSyncUtils.jsm @@ -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 }); diff --git a/toolkit/components/places/tests/unit/test_sync_utils.js b/toolkit/components/places/tests/unit/test_sync_utils.js index d458ab43dfc5..de7ecb6d3cd9 100644 --- a/toolkit/components/places/tests/unit/test_sync_utils.js +++ b/toolkit/components/places/tests/unit/test_sync_utils.js @@ -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"); diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini index 26b6e8a8c2e2..cb475a19afe6 100644 --- a/toolkit/content/tests/browser/browser.ini +++ b/toolkit/content/tests/browser/browser.ini @@ -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] diff --git a/toolkit/content/tests/browser/browser_crash_previous_frameloader.js b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js new file mode 100644 index 000000000000..63955dc7fd75 --- /dev/null +++ b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js @@ -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); + }); +}); diff --git a/tools/lint/flake8.lint b/tools/lint/flake8.lint index 810019f410b8..bc0cd76a5736 100644 --- a/tools/lint/flake8.lint +++ b/tools/lint/flake8.lint @@ -131,6 +131,7 @@ LINTER = { 'taskcluster', 'testing/firefox-ui', 'testing/marionette/client', + 'testing/marionette/harness', 'testing/puppeteer', 'testing/talos/', 'tools/lint', diff --git a/widget/tests/window_composition_text_querycontent.xul b/widget/tests/window_composition_text_querycontent.xul index d3031ee81091..6e5f974524e0 100644 --- a/widget/tests/window_composition_text_querycontent.xul +++ b/widget/tests/window_composition_text_querycontent.xul @@ -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

(can be computed with the rect of 'c') var p2 = synthesizeQueryTextRect(kLFLen + 3, 1); if (!checkQueryContentResult(p2, description + "rect for 2nd

")) { @@ -2292,10 +2320,17 @@ function runQueryTextRectInContentEditableTest() isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd

should be at similar to right of 'c'"); is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd

should be same height"); + // 2nd

as array + var p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1); + if (!checkQueryContentResult(p2AsArray, description + "2nd

's line breaker as array") || + !checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd

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

")) { + if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd

")) { 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

") || + !checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd

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 = "

abc

def


"; // \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

should be same height"); isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd

should be similar to right of 'f'"); + // 3rd

as array + var p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1); + if (!checkQueryContentResult(p3AsArray, description + "3rd

's line breaker as array") || + !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd

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

")) { + if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd

")) { 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

") || + !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd

should match with each query text rect result")) { + return; + } } //
in 3rd

@@ -2405,10 +2482,17 @@ function runQueryTextRectInContentEditableTest() isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by
in 3rd

should be similar height"); is(br.left, d.left, description + "left of a line breaker caused by
in 3rd

should be same left of 'd'"); + //
in 3rd

as array + var brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1); + if (!checkQueryContentResult(brAsArray, description + "
in 3rd

as array") || + !checkRectArray(brAsArray, [br], description + "query text rect array result of
in 3rd

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
in 3rd

")) { + if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by
in 3rd

")) { 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
in 3rd

") || + !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by
in 3rd

should match with each query text rect result")) { + return; + } } // next of
in 3rd

@@ -2429,6 +2520,13 @@ function runQueryTextRectInContentEditableTest() is(next_br.height, br.height, description + "next of
and
should be same height"); is(next_br.width, br.width, description + "next of
and
should be same width"); + // next of
in 3rd

as array + var next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1); + if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of
in 3rd

") || + !checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of
in 3rd

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

should be same height"); is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd

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 = "

abc

def

"; // \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

(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

")) { return; } @@ -2466,10 +2571,17 @@ function runQueryTextRectInContentEditableTest() is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd

should be same height"); isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd

should be similar to right of 'f'"); + // 3rd

as array + p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1); + if (!checkQueryContentResult(p3AsArray, description + "3rd

's line breaker as array") || + !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd

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

")) { + if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd

")) { 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

") || + !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd

should match with each query text rect result")) { + return; + } } // next of 3rd

@@ -2489,6 +2608,13 @@ function runQueryTextRectInContentEditableTest() isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd

should be at similar to left of 'd'"); isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd

and 'd' should be similar height"); + // next of 3rd

as array + var next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1); + if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd

as array") || + !checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd

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

should be same height"); is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd

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
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; + } + //
(can be computed with the rect of 'c') br = synthesizeQueryTextRect(3, 1); if (!checkQueryContentResult(br, description + "rect for
")) { @@ -2542,6 +2682,13 @@ function runQueryTextRectInContentEditableTest() isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by
should be at similar to right of 'c'"); is(br.height, c.height, description + "'c' and a line breaker caused by
should be same height"); + //
as array + brAsArray = synthesizeQueryTextRectArray(3, 1); + if (!checkQueryContentResult(brAsArray, description + "
's line breaker as array") || + !checkRectArray(brAsArray, [br], description + "query text rect array result of
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
") || + !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by
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
def
"; // \n 0123 4567 @@ -2633,10 +2808,17 @@ function runQueryTextRectInContentEditableTest() is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd
should be same height"); isSimilarTo(br2.left, f.left + f.width, 2, description + "left of a line breaker caused by 2nd
should be similar to right of 'f'"); + // 2nd
as array + var br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1); + if (!checkQueryContentResult(br2AsArray, description + "2nd
's line breaker as array") || + !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd
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
")) { + if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd
")) { 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
") || + !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd
should match with each query text rect result")) { + return; + } } // next of 2nd
@@ -2657,6 +2846,13 @@ function runQueryTextRectInContentEditableTest() is(next_br2.height, br2.height, description + "2nd
and next of 2nd
should be same height"); is(next_br2.width, br2.width, description + "2nd
and next of 2nd
should be same width"); + // next of 2nd
as array + var next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1); + if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd
") || + !checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd
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
should be same height"); is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd
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
def

"; // \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
should be same height"); ok(f.left < br2.left, description + "left of a line breaker caused by 2nd
should be bigger than left of 'f', f.left=" + f.left + ", br2.left=" + br2.left); + // 2nd
as array + br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1); + if (!checkQueryContentResult(br2AsArray, description + "2nd
's line breaker as array") || + !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd
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
")) { + if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd
")) { 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
") || + !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd
should match with each query text rect result")) { + return; + } } // 3rd
@@ -2717,10 +2934,17 @@ function runQueryTextRectInContentEditableTest() isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd
should be at similar to left of 'd'"); isSimilarTo(br3.height, d.height, 2, description + "next of 3rd
and 'd' should be similar height"); + // 3rd
as array + var br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1); + if (!checkQueryContentResult(br3AsArray, description + "3rd
's line breaker as array") || + !checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd
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
")) { + if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd
")) { 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
") || + !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd
should match with each query text rect result")) { + return; + } } // next of 3rd
@@ -2741,6 +2972,13 @@ function runQueryTextRectInContentEditableTest() is(next_br3.height, br3.height, description + "3rd
and next of 3rd
should be same height"); is(next_br3.width, br3.width, description + "3rd
and next of 3rd
should be same width"); + // next of 3rd
as array + var next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1); + if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd
") || + !checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd
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
should be at same left"); is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd
should be same height"); is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd
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; }