diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 77e7bdc8e822..066376591f1b 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -662,6 +662,14 @@ + + + + + + + + @@ -865,14 +873,6 @@ - - - - - - - - @@ -1527,6 +1527,14 @@ + + + + + + + + @@ -1714,7 +1722,7 @@ - + @@ -2043,6 +2051,14 @@ + + + + + + + + @@ -2164,6 +2180,11 @@ https://get.adobe.com/flashplayer/ + + + https://get.adobe.com/flashplayer/ + + @@ -2190,6 +2211,11 @@ + + + https://get.adobe.com/flashplayer/ + + https://get.adobe.com/flashplayer/ diff --git a/browser/components/sessionstore/SessionHistory.jsm b/browser/components/sessionstore/SessionHistory.jsm index 1bb286ca67d3..aa9c10379c10 100644 --- a/browser/components/sessionstore/SessionHistory.jsm +++ b/browser/components/sessionstore/SessionHistory.jsm @@ -384,29 +384,16 @@ var SessionHistoryInternal = { // FF55 will remove the triggeringPrincipal_b64, see Bug 1301666. if (entry.triggeringPrincipal_base64 || entry.principalToInherit_base64) { if (entry.triggeringPrincipal_base64) { - try { - shEntry.triggeringPrincipal = - Utils.deserializePrincipal(entry.triggeringPrincipal_base64); - } catch (e) { - debug(e); - } + shEntry.triggeringPrincipal = + Utils.deserializePrincipal(entry.triggeringPrincipal_base64); } if (entry.principalToInherit_base64) { - try { - shEntry.principalToInherit = - Utils.deserializePrincipal(entry.principalToInherit_base64); - } catch (e) { - debug(e); - } + shEntry.principalToInherit = + Utils.deserializePrincipal(entry.principalToInherit_base64); } } else if (entry.triggeringPrincipal_b64) { - try { - shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_b64); - shEntry.principalToInherit = shEntry.triggeringPrincipal; - } - catch (e) { - debug(e); - } + shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_b64); + shEntry.principalToInherit = shEntry.triggeringPrincipal; } if (entry.children && shEntry instanceof Ci.nsISHContainer) { diff --git a/browser/config/version.txt b/browser/config/version.txt index 68a97c4e7887..55d8e34dcf2f 100644 --- a/browser/config/version.txt +++ b/browser/config/version.txt @@ -1 +1 @@ -52.0.1 +52.0.2 diff --git a/browser/config/version_display.txt b/browser/config/version_display.txt index 68a97c4e7887..55d8e34dcf2f 100644 --- a/browser/config/version_display.txt +++ b/browser/config/version_display.txt @@ -1 +1 @@ -52.0.1 +52.0.2 diff --git a/browser/extensions/deployment-checker/README.md b/browser/extensions/deployment-checker/README.md new file mode 100644 index 000000000000..cd04877e53fd --- /dev/null +++ b/browser/extensions/deployment-checker/README.md @@ -0,0 +1,61 @@ +This system add-on attempts to confirm that users encounter Mozilla sites as +deployed by Mozilla. The add-on has a list of Mozilla properties (see after +this paragraph) and a list of expected certificate hashes. For each host, if +the add-on connects successfully to that host and determines that the +certificates sent are part of the web PKI (as in, the root is a built-in) yet +do not match the expected certificate chain, it will include in a telemetry +ping the host and the certificates in the chain (base64-encoded). The name of +the telemetry ping is "deployment-checker". The ping does not include the client +ID. + +The Mozilla properties queried are: + +* incoming.telemetry.mozilla.org +* telemetry.mozilla.org +* addons.mozilla.org +* services.addons.mozilla.org +* aus5.mozilla.org +* versioncheck.addons.mozilla.org +* support.mozilla.org +* ftp.mozilla.org +* mozilla.org +* bugzilla.mozilla.org +* crash-reports.mozilla.com +* releases.mozilla.com +* download-installer.cdn.mozilla.net +* firefox.settings.services.mozilla.com +* push.services.mozilla.com +* token.services.mozilla.com +* shavar.services.mozilla.com +* search.services.mozilla.com + +The report payload is a JSON dictionary containing two values: + +* version -- a version string to differentiate iterations of this add-on, if + necessary +* mismatches -- a list of objects with the properties: + * hostname -- the host for which a mismatch was detected + * chain -- a list of base64-encoded strings representing the bytes of the + certificates in the chain + +For example, if the add-on determined that the hosts "example1.mozilla.org" and +"example2.mozilla.org" were not sending the expected certificates (and yet they +verified correctly and were issued by a root in the web PKI), the payload may +look like: + + { "version": "1.0", + "mismatches": [ + { "hostname": "example1.mozilla.org", + "chain": [ + "MIIF8jCCBNqgAwIBAgIQDmTF+8I2reFLFyrrQceMsDANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIFNlcnZlciBDQTAeFw0xNTExMDMwMDAwMDBaFw0xODExMjgxMjAwMDBaMIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLTG9zIEFuZ2VsZXMxPDA6BgNVBAoTM0ludGVybmV0IENvcnBvcmF0aW9uIGZvciBBc3NpZ25lZCBOYW1lcyBhbmQgTnVtYmVyczETMBEGA1UECxMKVGVjaG5vbG9neTEYMBYGA1UEAxMPd3d3LmV4YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs0CWL2FjPiXBl61lRfvvE0KzLJmG9LWAC3bcBjgsH6NiVVo2dt6uXfzi5bTm7F3K7srfUBYkLO78mraM9qizrHoIeyofrV/n+pZZJauQsPjCPxMEJnRoD8Z4KpWKX0LyDu1SputoI4nlQ/htEhtiQnuoBfNZxF7WxcxGwEsZuS1KcXIkHl5VRJOreKFHTaXcB1qcZ/QRaBIv0yhxvK1yBTwWddT4cli6GfHcCe3xGMaSL328Fgs3jYrvG29PueB6VJi/tbbPu6qTfwp/H1brqdjh29U52Bhb0fJkM9DWxCP/Cattcc7az8EXnCO+LK8vkhw/kAiJWPKx4RBvgy73nwIDAQABo4ICUDCCAkwwHwYDVR0jBBgwFoAUUWj/kK8CB3U8zNllZGKiErhZcjswHQYDVR0OBBYEFKZPYB4fLdHn8SOgKpUW5Oia6m5IMIGBBgNVHREEejB4gg93d3cuZXhhbXBsZS5vcmeCC2V4YW1wbGUuY29tggtleGFtcGxlLmVkdYILZXhhbXBsZS5uZXSCC2V4YW1wbGUub3Jngg93d3cuZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmVkdYIPd3d3LmV4YW1wbGUubmV0MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItaGEtc2VydmVyLWc0LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItaGEtc2VydmVyLWc0LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAECAjCBgwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJIaWdoQXNzdXJhbmNlU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAISomhGn2L0LJn5SJHuyVZ3qMIlRCIdvqe0Q6ls+C8ctRwRO3UU3x8q8OH+2ahxlQmpzdC5al4XQzJLiLjiJ2Q1p+hub8MFiMmVPPZjb2tZm2ipWVuMRM+zgpRVM6nVJ9F3vFfUSHOb4/JsEIUvPY+d8/Krc+kPQwLvyieqRbcuFjmqfyPmUv1U9QoI4TQikpw7TZU0zYZANP4C/gj4Ry48/znmUaRvy2kvIl7gRQ21qJTK5suoiYoYNo3J9T+pXPGU7Lydz/HwW+w0DpArtAaukI8aNX4ohFUKSwDSiIIWIWJiJGbEeIO0TIFwEVWTOnbNl/faPXpk5IRXicapqiII=", + "MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3VyYW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC24C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMICKq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0Xsh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcftbZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7DaQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwdaOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNHE+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zuxICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0AecPUeybQ=" + ] + }, + { "hostname": "example2.mozilla.org", + "chain": [ + "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" + ] + } + ] + } diff --git a/browser/extensions/deployment-checker/bootstrap.js b/browser/extensions/deployment-checker/bootstrap.js new file mode 100644 index 000000000000..8a2451266a76 --- /dev/null +++ b/browser/extensions/deployment-checker/bootstrap.js @@ -0,0 +1,257 @@ +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/TelemetryController.jsm"); +var btoa = Cu.import("resource://gre/modules/Log.jsm").btoa; + +function certToBase64(cert) { + let derString = ""; + for (let rawByte of cert.getRawDER({})) { + derString += String.fromCharCode(rawByte); + } + return btoa(derString); +} + +function certArrayToBase64(certs) { + let result = []; + for (let cert of certs) { + result.push(certToBase64(cert)); + } + return result; +} + +function certListToJSArray(certList) { + let result = []; + let enumerator = certList.getEnumerator(); + while (enumerator.hasMoreElements()) { + let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert); + result.push(cert); + } + return result; +} + +class CertificateVerificationResult { + constructor(hostname, resolve) { + this.hostname = hostname; + this.resolve = resolve; + } + + verifyCertFinished(aPRErrorCode, aVerifiedChain, aEVStatus) { + let result = { hostname: this.hostname }; + if (aPRErrorCode == 0) { + result.chain = certListToJSArray(aVerifiedChain); + } else { + result.error = "certificate reverification"; + Services.console.logStringMessage(`${this.hostname}: ${aPRErrorCode}`); + } + this.resolve(result); + } +} + +function makeRequest(hostname) { + return new Promise((resolve) => { + let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + req.open("GET", "https://" + hostname); + req.timeout = 30000; + req.addEventListener("error", (evt) => { + resolve({ hostname, error: "connection error" }); + }); + req.addEventListener("timeout", (evt) => { + resolve({ hostname, error: "timeout" }); + }); + req.addEventListener("load", (evt) => { + let securityInfo = evt.target.channel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo); + if (securityInfo.securityState & + Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN) { + resolve({ hostname, error: "user override" }); + return; + } + let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) + .SSLStatus; + let certdb = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + let result = new CertificateVerificationResult(hostname, resolve); + // Unfortunately, we don't have direct access to the verified certificate + // chain as built by the AuthCertificate hook, so we have to re-build it + // here. In theory we are likely to get the same result. + certdb.asyncVerifyCertAtTime(sslStatus.serverCert, + 2, // certificateUsageSSLServer + 0, // flags + hostname, + Date.now() / 1000, + result); + }); + req.send(); + }); +} + +var sites = { + "incoming.telemetry.mozilla.org": [ + "63eb34876cbd2ebbc3b254961d96cdafb00f28719229f61e27b19a2510929012", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "telemetry.mozilla.org": [ + "197feaf3faa0f0ad637a89c97cb91336bfc114b6b3018203cbd9c3d10c7fa86c", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "addons.mozilla.org": [ + "51646c662bb3fd3a3bac9d976803f4e6869183bb483b7d30dcdfc5c4d0487b41", + "403e062a2653059113285baf80a0d4ae422c848c9f78fad01fc94bc5b87fef1a", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "services.addons.mozilla.org": [ + "51646c662bb3fd3a3bac9d976803f4e6869183bb483b7d30dcdfc5c4d0487b41", + "403e062a2653059113285baf80a0d4ae422c848c9f78fad01fc94bc5b87fef1a", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "aus5.mozilla.org": [ + "60e8e2e092bdc3b69ce260d6a52f90fd6368768600f911a22ee9f1b8833abeea", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "versioncheck.addons.mozilla.org": [ + "f7ac5873798f0322c206744901a8df1e944966be772e3a8bea2a4a9969fdfb38", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "support.mozilla.org": [ + "1751e120f14ddbd5306d037aaa0dd753e2989cc4f6e5560b6821a6f807525147", + "19400be5b7a31fb733917700789d2f0a2471c0c9d506c0e504c06c16d7cb17c0", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "ftp.mozilla.org": [ + "3b9ff6dc11f896b162603d29360be64e69f834e9b37a057a5b84cd54e58e7c8b", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "mozilla.org": [ + "8a43602dc67d8c5934fa638c2b066d385918a1c3f5fd5307d13a7b363cd526d3", + "403e062a2653059113285baf80a0d4ae422c848c9f78fad01fc94bc5b87fef1a", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "bugzilla.mozilla.org": [ + "1095a8c1e1c318fae495409911076de379abe5b02950ff40e8e863c4fdf39fcb", + "403e062a2653059113285baf80a0d4ae422c848c9f78fad01fc94bc5b87fef1a", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "crash-reports.mozilla.com": [ + "58fe74d89c13624f79c9c97bcf9f2da14d22eb1e8d1caeeaee0735f8e68ef4a5", + "403e062a2653059113285baf80a0d4ae422c848c9f78fad01fc94bc5b87fef1a", + "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf" + ], + "releases.mozilla.com": [ + "3b9ff6dc11f896b162603d29360be64e69f834e9b37a057a5b84cd54e58e7c8b", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "download-installer.cdn.mozilla.net": [ + "6442cb8d30d303bc67c685ba319e9497aa39aeffc3caca9a707f151071ab3ca8", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "firefox.settings.services.mozilla.com": [ + "ee6ddb1ac9614695a2c37579edb7844fa19fde18a490d1738e19cf0a49541918", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "push.services.mozilla.com": [ + "ad3ef2e8244aa2d3575189a34311b274ceb8e9be323fe48c843e1f66bb62f6fe", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "token.services.mozilla.com": [ + "dd123bd00f11e08d2995d907b80777edbff6169d2569d5d34f4fe10983d8901d", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "shavar.services.mozilla.com": [ + "ab0cab1d1d1157eb5dff0ea41cd6d1eeebf59d1f123042954c61ea78003457d0", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ], + "search.services.mozilla.com": [ + "e5bd9cc4248f835d9e8d359bcac7b3e5073890b67b8e1e070a322e3e09ab0754", + "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f", + "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161" + ] +}; + +function makeRequests() { + let promises = []; + for (let hostname of Object.keys(sites)) { + promises.push(makeRequest(hostname)); + } + return Promise.all(promises); +} + +function analyzeAndReport(results) { + let payload = { version: "1.0", mismatches: [] }; + Services.console.logStringMessage("deployment-checker results:"); + for (let result of results) { + // Skip if the connection resulted in any kind of error. + if ("error" in result) { + Services.console.logStringMessage(`${result.hostname}: ${result.error} - skipping`); + continue; + } + // Skip imported roots. + if (!result.chain[result.chain.length - 1].isBuiltInRoot) { + Services.console.logStringMessage(`${result.hostname}: imported root - skipping`); + continue; + } + + let report = false; + let expectedHashes = sites[result.hostname]; + // If we have chains of different length, obviously we'll have different + // chains, so report this chain. + if (expectedHashes.length != result.chain.length) { + report = true; + } else { + // Otherwise, compare each hash. If we encounter an unexpected one, report + // this chain. + for (let i = 0; i < expectedHashes.length; i++) { + let actualHash = result.chain[i].sha256Fingerprint.replace(/:/g, "") + .toLowerCase(); + if (actualHash != expectedHashes[i]) { + report = true; + break; + } + } + } + if (report) { + payload.mismatches.push({ hostname: result.hostname, + chain: certArrayToBase64(result.chain) }); + } else { + Services.console.logStringMessage(`${result.hostname} sends expected certificate chain`); + } + } + return TelemetryController.submitExternalPing("deployment-checker", payload, + {}); +} + +// We only run once - when installed. +function install() { + // Only run if we have a good indication that we're not in a testing + // environment (in which case attempting to connect to telemetry.mozilla.org + // will result in a test failure). + let telemetryServerURL = Preferences.get("toolkit.telemetry.server", + undefined); + // Also only run if the user has unified telemetry enabled (because we don't + // want to submit a telemetry ping if they've opted out). + let unifiedTelemetryEnabled = Preferences.get("toolkit.telemetry.unified", + undefined); + if (telemetryServerURL == "https://incoming.telemetry.mozilla.org" && + unifiedTelemetryEnabled === true) { + makeRequests().then(analyzeAndReport).catch(Cu.reportError); + } +} + +function startup() {} +function shutdown() {} +function uninstall() {} diff --git a/browser/extensions/deployment-checker/install.rdf.in b/browser/extensions/deployment-checker/install.rdf.in new file mode 100644 index 000000000000..a458cdb65c88 --- /dev/null +++ b/browser/extensions/deployment-checker/install.rdf.in @@ -0,0 +1,32 @@ + + + +#filter substitution + + + + + deployment-checker@mozilla.org + 1.0 + 2 + true + true + + + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + @MOZ_APP_VERSION@ + @MOZ_APP_MAXVERSION@ + + + + + Site Deployment Checker + Check that Users Encounter Mozilla Sites as Deployed by Mozilla + + diff --git a/browser/extensions/deployment-checker/moz.build b/browser/extensions/deployment-checker/moz.build new file mode 100644 index 000000000000..fe8d072c806c --- /dev/null +++ b/browser/extensions/deployment-checker/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION'] +DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION'] + +FINAL_TARGET_FILES.features['deployment-checker@mozilla.org'] += [ + 'bootstrap.js' +] + +FINAL_TARGET_PP_FILES.features['deployment-checker@mozilla.org'] += [ + 'install.rdf.in' +] diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build index 5e1bfc459f2c..17b616466eac 100644 --- a/browser/extensions/moz.build +++ b/browser/extensions/moz.build @@ -6,6 +6,7 @@ DIRS += [ 'aushelper', + 'deployment-checker', 'e10srollout', 'pdfjs', 'webcompat', diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi index b3b952b1a2a7..f430962c0782 100755 --- a/browser/installer/windows/nsis/installer.nsi +++ b/browser/installer/windows/nsis/installer.nsi @@ -594,6 +594,7 @@ Section "-InstallEndCleanup" SetDetailsPrint none ${Unless} ${Silent} + ClearErrors ${MUI_INSTALLOPTIONS_READ} $0 "summary.ini" "Field 4" "State" ${If} "$0" == "1" ; NB: this code is duplicated in stub.nsi. Please keep in sync. @@ -632,7 +633,7 @@ Section "-InstallEndCleanup" GetFunctionAddress $0 SetAsDefaultAppUserHKCU UAC::ExecCodeSegment $0 ${EndIf} - ${Else} + ${ElseIfNot} ${Errors} ${LogHeader} "Writing default-browser opt-out" ClearErrors WriteRegStr HKCU "Software\Mozilla\Firefox" "DefaultBrowserOptOut" "True" diff --git a/config/milestone.txt b/config/milestone.txt index f6c92843cf31..9a1d75bad9e0 100644 --- a/config/milestone.txt +++ b/config/milestone.txt @@ -10,4 +10,4 @@ # hardcoded milestones in the tree from these two files. #-------------------------------------------------------- -52.0.1 +52.0.2 diff --git a/dom/animation/EffectCompositor.cpp b/dom/animation/EffectCompositor.cpp index 458511d93d60..c88cabe90bba 100644 --- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -619,7 +619,7 @@ EffectCompositor::ComposeAnimationRule(dom::Element* aElement, // priority except properties in propertiesToSkip. const nsCSSPropertyIDSet& propertiesToSkip = aCascadeLevel == CascadeLevel::Animations - ? nsCSSPropertyIDSet() + ? effects->PropertiesForAnimationsLevel().Invert() : effects->PropertiesForAnimationsLevel(); for (KeyframeEffectReadOnly* effect : sortedEffectList) { effect->GetAnimation()->ComposeStyle(animationRule, propertiesToSkip); diff --git a/gfx/skia/skia/src/gpu/SkGpuDevice.cpp b/gfx/skia/skia/src/gpu/SkGpuDevice.cpp index d4d361d4fb9f..248a240f0143 100644 --- a/gfx/skia/skia/src/gpu/SkGpuDevice.cpp +++ b/gfx/skia/skia/src/gpu/SkGpuDevice.cpp @@ -1034,7 +1034,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap, SkMatrix texMatrix; // Compute a matrix that maps the rect we will draw to the src rect. - texMatrix.setRectToRect(dstRect, srcRect, SkMatrix::kStart_ScaleToFit); + texMatrix.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit); texMatrix.postScale(iw, ih); // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 6704d7f4111f..55edbf89e47c 100755 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -728,6 +728,12 @@ gfxWindowsPlatform::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh, aFontList.AppendElement(kFontUtsaah); aFontList.AppendElement(kFontAparajita); break; + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + aFontList.AppendElement(kFontNirmalaUI); + break; case 0x0e: aFontList.AppendElement(kFontLaoUI); break; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 59b4608eb02c..49fef2bf9552 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8921,14 +8921,30 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, } if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) { - TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + // AsyncMethod[Yield, Await]: + // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... + // + // PropertyName: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + // + // ComputedPropertyName[Yield, Await]: + // [ ... + TokenKind tt = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::KeywordIsName)) return null(); - if (tt != TOK_LP && tt != TOK_COLON && tt != TOK_RC && tt != TOK_ASSIGN) { + if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB || + tt == TOK_NAME || tt == TOK_YIELD) + { isAsync = true; + tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName); ltok = tt; } else { - tokenStream.ungetToken(); tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); } } diff --git a/js/src/tests/ecma_7/AsyncFunctions/property.js b/js/src/tests/ecma_7/AsyncFunctions/property.js index 6c3a3697a6fd..53f779c330c1 100644 --- a/js/src/tests/ecma_7/AsyncFunctions/property.js +++ b/js/src/tests/ecma_7/AsyncFunctions/property.js @@ -35,5 +35,15 @@ print(BUGNUMBER + ": " + summary); assertEq(a, 14); } +{ + let { async, other } = { async: 15, other: 16 }; + assertEq(async, 15); + assertEq(other, 16); + + let a = { async, other }; + assertEq(a.async, 15); + assertEq(a.other, 16); +} + if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/layout/reftests/css-transitions/reftest.list b/layout/reftests/css-transitions/reftest.list index e4e8c7adf574..f8385e2e63f0 100644 --- a/layout/reftests/css-transitions/reftest.list +++ b/layout/reftests/css-transitions/reftest.list @@ -6,3 +6,4 @@ == stacking-context-transform-lose-to-animation.html stacking-context-transition-ref.html == stacking-context-opacity-wins-over-important-style.html stacking-context-transition-ref.html == stacking-context-transform-wins-over-important-style.html stacking-context-transition-ref.html +== transition-and-animation-with-different-durations.html transition-and-animation-with-different-durations-ref.html diff --git a/layout/reftests/css-transitions/transition-and-animation-with-different-durations-ref.html b/layout/reftests/css-transitions/transition-and-animation-with-different-durations-ref.html new file mode 100644 index 000000000000..e6850238afa0 --- /dev/null +++ b/layout/reftests/css-transitions/transition-and-animation-with-different-durations-ref.html @@ -0,0 +1,9 @@ + +A reference of 100x100 blue box + +
diff --git a/layout/reftests/css-transitions/transition-and-animation-with-different-durations.html b/layout/reftests/css-transitions/transition-and-animation-with-different-durations.html new file mode 100644 index 000000000000..24e3177a31a5 --- /dev/null +++ b/layout/reftests/css-transitions/transition-and-animation-with-different-durations.html @@ -0,0 +1,31 @@ + + + +Transform animation and opacity transition with different durations + + +
+ diff --git a/layout/style/nsCSSPropertyIDSet.h b/layout/style/nsCSSPropertyIDSet.h index 19a2eebebbcc..4de0471d18f6 100644 --- a/layout/style/nsCSSPropertyIDSet.h +++ b/layout/style/nsCSSPropertyIDSet.h @@ -67,6 +67,15 @@ class nsCSSPropertyIDSet { return mozilla::PodEqual(mProperties, aOther.mProperties); } + // Return a new nsCSSPropertyIDSet which is the inverse of this set. + nsCSSPropertyIDSet Invert() const { + nsCSSPropertyIDSet result; + for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) { + result.mProperties[i] = ~mProperties[i]; + } + return result; + } + private: typedef unsigned long property_set_type; public: diff --git a/layout/tools/reftest/reftestcommandline.py b/layout/tools/reftest/reftestcommandline.py index a47e46fe681c..99e8c2f284f8 100644 --- a/layout/tools/reftest/reftestcommandline.py +++ b/layout/tools/reftest/reftestcommandline.py @@ -643,8 +643,8 @@ def __init__(self, **kwargs): action="store", type=str, dest="dm_trans", - default="sut", - help="the transport to use to communicate with device: [adb|sut]; default=sut") + default="adb", + help="the transport to use to communicate with device: [adb|sut]; default=adb") self.add_argument("--remoteTestRoot", action="store", diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 10dbcf1c1217..b629c110fa1a 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -121,8 +121,8 @@ pref("network.predictor.enabled", true); pref("network.predictor.max-db-size", 2097152); // bytes pref("network.predictor.preserve", 50); // percentage of predictor data to keep when cleaning up -// Use JS mDNS as a fallback -pref("network.mdns.use_js_fallback", true); +// Do not use JS mDNS as a fallback +pref("network.mdns.use_js_fallback", false); /* history max results display */ pref("browser.display.history.maxresults", 100); @@ -907,8 +907,8 @@ pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sy // Enable Presentation API pref("dom.presentation.enabled", false); -pref("dom.presentation.discovery.enabled", true); -pref("dom.presentation.discovery.legacy.enabled", true); // for TV 2.5 backward capability +pref("dom.presentation.discovery.enabled", false); +pref("dom.presentation.discovery.legacy.enabled", false); // for TV 2.5 backward capability pref("dom.audiochannel.audioCompeting", true); pref("dom.audiochannel.mediaControl", true); diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java index 5eddca3cf726..a05b64722123 100644 --- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java @@ -61,6 +61,7 @@ import org.mozilla.gecko.media.VideoPlayer; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.GeckoMenuItem; +import org.mozilla.gecko.mozglue.GeckoLoader; import org.mozilla.gecko.mozglue.SafeIntent; import org.mozilla.gecko.notifications.NotificationHelper; import org.mozilla.gecko.overlays.ui.ShareDialog; @@ -2082,6 +2083,10 @@ public void run() { break; case "Gecko:Ready": + if (!GeckoLoader.neonCompatible()) { + conditionallyNotifyEOL(); + } + // Handle this message in GeckoApp, but also enable the Settings // menuitem, which is specific to BrowserApp. super.handleMessage(event, message); diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index 25e3de6cdc0c..18ac6bf940c2 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -1365,30 +1365,16 @@ SessionStore.prototype = { // FF55 will remove the triggeringPrincipal_b64, see Bug 1301666. if (aEntry.triggeringPrincipal_base64 || aEntry.principalToInherit_base64) { if (aEntry.triggeringPrincipal_base64) { - try { - shEntry.triggeringPrincipal = - Utils.deserializePrincipal(aEntry.triggeringPrincipal_base64); - } - catch (e) { - dump(e); - } + shEntry.triggeringPrincipal = + Utils.deserializePrincipal(aEntry.triggeringPrincipal_base64); } if (aEntry.principalToInherit_base64) { - try { - shEntry.principalToInherit = - Utils.deserializePrincipal(aEntry.principalToInherit_base64); - } catch (e) { - dump(e); - } + shEntry.principalToInherit = + Utils.deserializePrincipal(aEntry.principalToInherit_base64); } } else if (aEntry.triggeringPrincipal_b64) { - try { - shEntry.triggeringPrincipal = Utils.deserializePrincipal(aEntry.triggeringPrincipal_b64); - shEntry.principalToInherit = shEntry.triggeringPrincipal; - } - catch (e) { - dump(e); - } + shEntry.triggeringPrincipal = Utils.deserializePrincipal(aEntry.triggeringPrincipal_b64); + shEntry.principalToInherit = shEntry.triggeringPrincipal; } if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java index 0bef2435bb00..93c0675891c6 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java @@ -546,4 +546,5 @@ public static void abort(final String msg) { private static native void loadSQLiteLibsNative(String apkName); private static native void loadNSSLibsNative(String apkName); private static native void extractGeckoLibsNative(String apkName); + public static native boolean neonCompatible(); } diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp index 1aabc155ebd4..0995e1664aee 100644 --- a/mozglue/android/APKOpen.cpp +++ b/mozglue/android/APKOpen.cpp @@ -33,6 +33,7 @@ #include "ElfLoader.h" #include "application.ini.h" +#include "mozilla/arm.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "XREChildData.h" @@ -463,3 +464,12 @@ ChildProcessInit(int argc, char* argv[]) return fXRE_InitChildProcess(argc, argv, &childData); } +extern "C" NS_EXPORT jboolean MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_neonCompatible(JNIEnv *jenv, jclass jc) +{ +#ifdef __ARM_EABI__ + return mozilla::supports_neon(); +#else + return true; +#endif // __ARM_EABI__ +} diff --git a/testing/config/tooltool-manifests/linux64/geckodriver.manifest b/testing/config/tooltool-manifests/linux64/geckodriver.manifest index d772c947e43b..2f11f33c7178 100644 --- a/testing/config/tooltool-manifests/linux64/geckodriver.manifest +++ b/testing/config/tooltool-manifests/linux64/geckodriver.manifest @@ -1,9 +1,9 @@ [ -{ -"size": 1455774, -"visibility": "public", -"digest": "0f552a18276ff98985b236cd5e2f8baa5e4edcbbee1d8c21e3e5a5f61f2a674e5fb355f8be3b1bfb2a1f34465721d7ded12f432a50877c78c4b039ce0fcf6b8d", -"algorithm": "sha512", -"filename": "geckodriver-v0.11.1-linux64.tar.gz" -} + { + "size": 1801469, + "visibility": "public", + "digest": "d91c2dcea4dc4eeef3dfba8965c207060fd77a9fc108230c44ae2b60650c9879da7600e0df53f2cd29d28705d139e594493917f45a6c5061c0b516f65687a4b4", + "algorithm": "sha512", + "filename": "geckodriver-v0.13.0-linux64.tar.gz" + } ] diff --git a/testing/docker/desktop1604-test/Dockerfile b/testing/docker/desktop1604-test/Dockerfile index db81cc51f2f5..929b167c2138 100644 --- a/testing/docker/desktop1604-test/Dockerfile +++ b/testing/docker/desktop1604-test/Dockerfile @@ -86,7 +86,10 @@ RUN rm -Rf .cache && mkdir -p .cache ADD release-upgrades /etc/update-manager/release-upgrades # Disable tools with on-login popups that interfere with tests; see bug 1240084 and bug 984944. -ADD jockey-gtk.desktop deja-dup-monitor.desktop /etc/xdg/autostart/ +ADD autostart/jockey-gtk.desktop autostart/deja-dup-monitor.desktop /etc/xdg/autostart/ + +# Bug 1345105 - Do not run periodical update checks and downloads +ADD autostart/gnome-software-service.desktop /etc/xdg/autostart/ # In test.sh we accept START_VNC to start a vnc daemon. # Exposing this port allows it to work. diff --git a/testing/docker/desktop1604-test/deja-dup-monitor.desktop b/testing/docker/desktop1604-test/autostart/deja-dup-monitor.desktop similarity index 100% rename from testing/docker/desktop1604-test/deja-dup-monitor.desktop rename to testing/docker/desktop1604-test/autostart/deja-dup-monitor.desktop diff --git a/testing/docker/desktop1604-test/autostart/gnome-software-service.desktop b/testing/docker/desktop1604-test/autostart/gnome-software-service.desktop new file mode 100644 index 000000000000..b563cc306b06 --- /dev/null +++ b/testing/docker/desktop1604-test/autostart/gnome-software-service.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=GNOME Software +Exec=/usr/bin/gnome-software --gapplication-service +OnlyShowIn=GNOME;Unity; +X-Ubuntu-Gettext-Domain=gnome-software + +# Bug 1345105 - Do not run periodical update checks and downloads +X-GNOME-Autostart-enabled=false diff --git a/testing/docker/desktop1604-test/jockey-gtk.desktop b/testing/docker/desktop1604-test/autostart/jockey-gtk.desktop similarity index 100% rename from testing/docker/desktop1604-test/jockey-gtk.desktop rename to testing/docker/desktop1604-test/autostart/jockey-gtk.desktop diff --git a/testing/docker/firefox-snap/runme.sh b/testing/docker/firefox-snap/runme.sh old mode 100644 new mode 100755 diff --git a/testing/docker/funsize-update-generator/scripts/funsize.py b/testing/docker/funsize-update-generator/scripts/funsize.py old mode 100644 new mode 100755 diff --git a/testing/docker/funsize-update-generator/scripts/mbsdiff_hook.sh b/testing/docker/funsize-update-generator/scripts/mbsdiff_hook.sh old mode 100644 new mode 100755 diff --git a/testing/marionette/action.js b/testing/marionette/action.js index daf04d023d61..2eb39810be4e 100644 --- a/testing/marionette/action.js +++ b/testing/marionette/action.js @@ -327,11 +327,46 @@ const KEY_CODE_LOOKUP = { "\uE004": "Tab", }; +/** Represents possible values for a pointer-move origin. */ +action.PointerOrigin = { + Viewport: "viewport", + Pointer: "pointer", +}; + +/** + * Look up a PointerOrigin. + * + * @param {?} obj + * Origin for a pointerMove action. + * + * @return {?} + * A pointer origin that is either "viewport" (default), "pointer", or a + * web-element reference. + * + * @throws {InvalidArgumentError} + * If |obj| is not a valid origin. + */ +action.PointerOrigin.get = function(obj) { + let origin = obj; + if (typeof obj == "undefined") { + origin = this.Viewport; + } else if (typeof obj == "string") { + let name = capitalize(obj); + assert.in(name, this, error.pprint`Unknown pointer-move origin: ${obj}`); + origin = this[name]; + } else if (!element.isWebElementReference(obj)) { + throw new InvalidArgumentError("Expected 'origin' to be a string or a " + + `web element reference, got: ${obj}`); + } + return origin; +}; + /** Represents possible subtypes for a pointer input source. */ action.PointerType = { Mouse: "mouse", - Pen: "pen", - Touch: "touch", + // TODO For now, only mouse is supported + //Pen: "pen", + //Touch: "touch", }; /** @@ -348,9 +383,7 @@ action.PointerType = { */ action.PointerType.get = function (str) { let name = capitalize(str); - if (!(name in this)) { - throw new InvalidArgumentError(`Unknown pointerType: ${str}`); - } + assert.in(name, this, error.pprint`Unknown pointerType: ${str}`); return this[name]; }; @@ -400,8 +433,8 @@ class InputState { /** * @param {?} obj - * Object with property |type|, representing an action sequence or an - * action item. + * Object with property |type| and optionally |parameters| or |pointerType|, + * representing an action sequence or an action item. * * @return {action.InputState} * An |action.InputState| object for the type of the |actionSequence|. @@ -411,11 +444,18 @@ class InputState { */ static fromJson(obj) { let type = obj.type; - if (!(type in ACTIONS)) { - throw new InvalidArgumentError(`Unknown action type: ${type}`); - } + assert.in(type, ACTIONS, error.pprint`Unknown action type: ${type}`); let name = type == "none" ? "Null" : capitalize(type); - return new action.InputState[name](); + if (name == "Pointer") { + if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) { + throw new InvalidArgumentError( + error.pprint`Expected obj to have pointerType, got: ${obj}`); + } + let pointerType = obj.pointerType || obj.parameters.pointerType; + return new action.InputState[name](pointerType); + } else { + return new action.InputState[name](); + } } } @@ -488,7 +528,7 @@ action.InputState.Key = class Key extends InputState { * Normalized key value. * * @return {boolean} - * True if |key| is removed successfully, false otherwise. + * True if |key| was present before removal, false otherwise. */ release(key) { return this.pressed.delete(key); @@ -510,18 +550,61 @@ action.InputState.Null = class Null extends InputState { * * @param {string} subtype * Kind of pointing device: mouse, pen, touch. - * @param {boolean} primary - * Whether the pointing device is primary. + * + * @throws {InvalidArgumentError} + * If subtype is undefined or an invalid pointer type. */ action.InputState.Pointer = class Pointer extends InputState { - constructor(subtype, primary) { + constructor(subtype) { super(); this.pressed = new Set(); - this.subtype = subtype; - this.primary = primary; + assert.defined(subtype, error.pprint`Expected subtype to be defined, got: ${subtype}`); + this.subtype = action.PointerType.get(subtype); this.x = 0; this.y = 0; } + + /** + * Check whether |button| is pressed. + * + * @param {number} button + * Positive integer that refers to a mouse button. + * + * @return {boolean} + * True if |button| is in set of pressed buttons. + */ + isPressed(button) { + assert.positiveInteger(button); + return this.pressed.has(button); + } + + /** + * Add |button| to the set of pressed keys. + * + * @param {number} button + * Positive integer that refers to a mouse button. + * + * @return {Set} + * Set of pressed buttons. + */ + press(button) { + assert.positiveInteger(button); + return this.pressed.add(button); + } + + /** + * Remove |button| from the set of pressed buttons. + * + * @param {number} button + * A positive integer that refers to a mouse button. + * + * @return {boolean} + * True if |button| was present before removals, false otherwise. + */ + release(button) { + assert.positiveInteger(button); + return this.pressed.delete(button); + } }; /** @@ -543,9 +626,7 @@ action.Action = class { throw new InvalidArgumentError("Missing id, type or subtype"); } for (let attr of [id, type, subtype]) { - if (typeof attr != "string") { - throw new InvalidArgumentError(`Expected string, got: ${attr}`); - } + assert.string(attr, error.pprint`Expected string, got: ${attr}`); } this.id = id; this.type = type; @@ -595,11 +676,9 @@ action.Action = class { // TODO countGraphemes // TODO key.value could be a single code point like "\uE012" (see rawKey) // or "grapheme cluster" - if (typeof key != "string") { - throw new InvalidArgumentError( - "Expected 'value' to be a string that represents single code point " + - "or grapheme cluster, got: " + key); - } + assert.string(key, + error.pprint("Expected 'value' to be a string that represents single code point " + + `or grapheme cluster, got: ${key}`)); item.value = key; break; @@ -616,21 +695,14 @@ action.Action = class { assert.positiveInteger(item.duration, error.pprint`Expected 'duration' (${item.duration}) to be >= 0`); } - if (typeof actionItem.element != "undefined" && - !element.isWebElementReference(actionItem.element)) { - throw new InvalidArgumentError( - "Expected 'actionItem.element' to be a web element reference, " + - `got: ${actionItem.element}`); - } - item.element = actionItem.element; - + item.origin = action.PointerOrigin.get(actionItem.origin); item.x = actionItem.x; if (typeof item.x != "undefined") { - assert.positiveInteger(item.x, error.pprint`Expected 'x' (${item.x}) to be >= 0`); + assert.integer(item.x, error.pprint`Expected 'x' (${item.x}) to be an Integer`); } item.y = actionItem.y; if (typeof item.y != "undefined") { - assert.positiveInteger(item.y, error.pprint`Expected 'y' (${item.y}) to be >= 0`); + assert.integer(item.y, error.pprint`Expected 'y' (${item.y}) to be an Integer`); } break; @@ -672,7 +744,7 @@ action.Chain = class extends Array { */ static fromJson(actions) { assert.array(actions, - error.pprint`Expected 'actions' to be an Array, got: ${actions}`); + error.pprint`Expected 'actions' to be an Array, got: ${actions}`); let actionsByTick = new action.Chain(); // TODO check that each actionSequence in actions refers to a different input ID for (let actionSequence of actions) { @@ -710,18 +782,18 @@ action.Sequence = class extends Array { * If |actionSequence.actions| is not an Array. */ static fromJson(actionSequence) { - // used here only to validate 'type' and InputState type + // used here to validate 'type' in addition to InputState type below let inputSourceState = InputState.fromJson(actionSequence); let id = actionSequence.id; assert.defined(id, "Expected 'id' to be defined"); assert.string(id, error.pprint`Expected 'id' to be a string, got: ${id}`); let actionItems = actionSequence.actions; - if (!Array.isArray(actionItems)) { - throw new InvalidArgumentError( - `Expected 'actionSequence.actions' to be an Array, got: ${actionSequence.actions}`); - } - - if (action.inputStateMap.has(id) && !action.inputStateMap.get(id).is(inputSourceState)) { + assert.array(actionItems, + error.pprint("Expected 'actionSequence.actions' to be an Array, " + + `got: ${actionSequence.actions}`)); + if (!action.inputStateMap.has(id)) { + action.inputStateMap.set(id, inputSourceState); + } else if (!action.inputStateMap.get(id).is(inputSourceState)) { throw new InvalidArgumentError( `Expected ${id} to be mapped to ${inputSourceState}, ` + `got: ${action.inputStateMap.get(id)}`); @@ -739,19 +811,14 @@ action.Sequence = class extends Array { * * @param {string=} pointerType * Type of pointing device. If the parameter is undefined, "mouse" is used. - * @param {boolean=} primary - * Whether the input source is the primary pointing device. - * If the parameter is undefined, true is used. */ action.PointerParameters = class { - constructor(pointerType = "mouse", primary = true) { + constructor(pointerType = "mouse") { this.pointerType = action.PointerType.get(pointerType); - assert.boolean(primary); - this.primary = primary; } toString() { - return `[pointerParameters ${this.pointerType}, primary=${this.primary}]`; + return `[pointerParameters ${this.pointerType}]`; } /** @@ -765,13 +832,13 @@ action.PointerParameters = class { if (typeof parametersData == "undefined") { return new action.PointerParameters(); } else { - return new action.PointerParameters(parametersData.pointerType, parametersData.primary); + return new action.PointerParameters(parametersData.pointerType); } } }; /** - * Adds |pointerType| and |primary| attributes to Action |act|. Helper function + * Adds |pointerType| attribute to Action |act|. Helper function * for |action.Action.fromJson|. * * @param {string} id @@ -783,17 +850,21 @@ action.PointerParameters = class { * * @throws {InvalidArgumentError} * If |id| is already mapped to an |action.InputState| that is - * not compatible with |act.subtype|. + * not compatible with |act.type| or |pointerParams.pointerType|. */ action.processPointerAction = function processPointerAction(id, pointerParams, act) { - let subtype = act.subtype; - if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== subtype) { + if (action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type) { + throw new InvalidArgumentError( + `Expected 'id' ${id} to be mapped to InputState whose type is ` + + `${action.inputStateMap.get(id).type}, got: ${act.type}`); + } + let pointerType = pointerParams.pointerType; + if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType) { throw new InvalidArgumentError( `Expected 'id' ${id} to be mapped to InputState whose subtype is ` + - `${action.inputStateMap.get(id).subtype}, got: ${subtype}`); + `${action.inputStateMap.get(id).subtype}, got: ${pointerType}`); } act.pointerType = pointerParams.pointerType; - act.primary = pointerParams.primary; }; /** Collect properties associated with KeyboardEvent */ @@ -820,6 +891,21 @@ action.Key = class { } }; +/** Collect properties associated with MouseEvent */ +action.Mouse = class { + constructor(type, button = 0) { + this.type = type; + assert.positiveInteger(button); + this.button = button; + this.buttons = 0; + } + + update(inputState) { + let allButtons = Array.from(inputState.pressed); + this.buttons = allButtons.reduce((a, i) => a + Math.pow(2, i), 0); + } +}; + /** * Dispatch a chain of actions over |chain.length| ticks. * @@ -896,6 +982,40 @@ action.computeTickDuration = function(tickActions) { return max; }; +/** + * Compute viewport coordinates of pointer target based on given origin. + * + * @param {action.Action} a + * Action that specifies pointer origin and x and y coordinates of target. + * @param {action.InputState} inputState + * Input state that specifies current x and y coordinates of pointer. + * @param {Map.=} center + * Object representing x and y coordinates of an element center-point. + * This is only used if |a.origin| is a web element reference. + * + * @return {Map.} + * x and y coordinates of pointer destination. + */ +action.computePointerDestination = function(a, inputState, center = undefined) { + let {x, y} = a; + switch (a.origin) { + case action.PointerOrigin.Viewport: + break; + case action.PointerOrigin.Pointer: + x += inputState.x; + y += inputState.y; + break; + default: + // origin represents web element + assert.defined(center); + assert.in("x", center); + assert.in("y", center); + x += center.x; + y += center.y; + } + return {"x": x, "y": y}; +}; + /** * Create a closure to use as a map from action definitions to Promise events. * @@ -912,9 +1032,6 @@ action.computeTickDuration = function(tickActions) { */ function toEvents(tickDuration, seenEls, container) { return function (a) { - if (!action.inputStateMap.has(a.id)) { - action.inputStateMap.set(a.id, InputState.fromJson(a)); - } let inputState = action.inputStateMap.get(a.id); switch (a.subtype) { case action.KeyUp: @@ -924,8 +1041,14 @@ function toEvents(tickDuration, seenEls, container) { return dispatchKeyDown(a, inputState, container.frame); case action.PointerDown: + return dispatchPointerDown(a, inputState, container.frame); + case action.PointerUp: + return dispatchPointerUp(a, inputState, container.frame); + case action.PointerMove: + return dispatchPointerMove(a, inputState, tickDuration, seenEls, container); + case action.PointerCancel: throw new UnsupportedOperationError(); @@ -996,6 +1119,180 @@ function dispatchKeyUp(a, inputState, win) { }); } +/** + * Dispatch a pointerDown action equivalent to pressing a pointer-device + * button. + * + * @param {action.Action} a + * Action to dispatch. + * @param {action.InputState} inputState + * Input state for this action's input source. + * @param {nsIDOMWindow} win + * Current window. + * + * @return {Promise} + * Promise to dispatch at least a pointerdown event. + */ +function dispatchPointerDown(a, inputState, win) { + return new Promise(resolve => { + if (inputState.isPressed(a.button)) { + resolve(); + return; + } + inputState.press(a.button); + // Append a copy of |a| with pointerUp subtype + action.inputsToCancel.push(Object.assign({}, a, {subtype: action.PointerUp})); + switch (inputState.subtype) { + case action.PointerType.Mouse: + let mouseEvent = new action.Mouse("mousedown", a.button); + mouseEvent.update(inputState); + event.synthesizeMouseAtPoint(inputState.x, inputState.y, mouseEvent, win); + break; + case action.PointerType.Pen: + case action.PointerType.Touch: + throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); + break; + default: + throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); + } + resolve(); + }); +} + +/** + * Dispatch a pointerUp action equivalent to releasing a pointer-device + * button. + * + * @param {action.Action} a + * Action to dispatch. + * @param {action.InputState} inputState + * Input state for this action's input source. + * @param {nsIDOMWindow} win + * Current window. + * + * @return {Promise} + * Promise to dispatch at least a pointerup event. + */ +function dispatchPointerUp(a, inputState, win) { + return new Promise(resolve => { + if (!inputState.isPressed(a.button)) { + resolve(); + return; + } + inputState.release(a.button); + switch (inputState.subtype) { + case action.PointerType.Mouse: + let mouseEvent = new action.Mouse("mouseup", a.button); + mouseEvent.update(inputState); + event.synthesizeMouseAtPoint(inputState.x, inputState.y, + mouseEvent, win); + break; + case action.PointerType.Pen: + case action.PointerType.Touch: + throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); + default: + throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); + } + resolve(); + }); +} + +/** + * Dispatch a pointerMove action equivalent to moving pointer device in a line. + * + * If the action duration is 0, the pointer jumps immediately to the target coordinates. + * Otherwise, events are synthesized to mimic a pointer travelling in a discontinuous, + * approximately straight line, with the pointer coordinates being updated around 60 + * times per second. + * + * @param {action.Action} a + * Action to dispatch. + * @param {action.InputState} inputState + * Input state for this action's input source. + * @param {element.Store} seenEls + * Element store. + * @param {?} container + * Object with |frame| attribute of type |nsIDOMWindow|. + * + * @return {Promise} + * Promise to dispatch at least one pointermove event, as well as mousemove events + * as appropriate. + */ +function dispatchPointerMove(a, inputState, tickDuration, seenEls, container) { + const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // interval between pointermove increments in ms, based on common vsync + const fps60 = 17; + return new Promise(resolve => { + const start = Date.now(); + const [startX, startY] = [inputState.x, inputState.y]; + let target = action.computePointerDestination(a, inputState, + getElementCenter(a.origin, seenEls, container)); + const [targetX, targetY] = [target.x, target.y]; + if (!inViewPort(targetX, targetY, container.frame)) { + throw new MoveTargetOutOfBoundsError( + `(${targetX}, ${targetY}) is out of bounds of viewport ` + + `width (${container.frame.innerWidth}) and height (${container.frame.innerHeight})`); + } + + const duration = typeof a.duration == "undefined" ? tickDuration : a.duration; + if (duration === 0) { + // move pointer to destination in one step + performOnePointerMove(inputState, targetX, targetY, container.frame); + resolve(); + return; + } + + const distanceX = targetX - startX; + const distanceY = targetY - startY; + const ONE_SHOT = Ci.nsITimer.TYPE_ONE_SHOT; + let intermediatePointerEvents = Task.spawn(function* () { + // wait |fps60| ms before performing first incremental pointer move + yield new Promise(resolveTimer => + timer.initWithCallback(resolveTimer, fps60, ONE_SHOT) + ); + let durationRatio = Math.floor(Date.now() - start) / duration; + const epsilon = fps60 / duration / 10; + while ((1 - durationRatio) > epsilon) { + let x = Math.floor(durationRatio * distanceX + startX); + let y = Math.floor(durationRatio * distanceY + startY); + performOnePointerMove(inputState, x, y, container.frame); + // wait |fps60| ms before performing next pointer move + yield new Promise(resolveTimer => + timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)); + durationRatio = Math.floor(Date.now() - start) / duration; + } + }); + // perform last pointer move after all incremental moves are resolved and + // durationRatio is close enough to 1 + intermediatePointerEvents.then(() => { + performOnePointerMove(inputState, targetX, targetY, container.frame); + resolve(); + }); + + }); +} + +function performOnePointerMove(inputState, targetX, targetY, win) { + if (targetX == inputState.x && targetY == inputState.y) { + return; + } + switch (inputState.subtype) { + case action.PointerType.Mouse: + let mouseEvent = new action.Mouse("mousemove"); + mouseEvent.update(inputState); + //TODO both pointermove (if available) and mousemove + event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win); + break; + case action.PointerType.Pen: + case action.PointerType.Touch: + throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); + default: + throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); + } + inputState.x = targetX; + inputState.y = targetY; +} + /** * Dispatch a pause action equivalent waiting for |a.duration| milliseconds, or a * default time interval of |tickDuration|. @@ -1009,10 +1306,10 @@ function dispatchKeyUp(a, inputState, win) { * Promise that is resolved after the specified time interval. */ function dispatchPause(a, tickDuration) { - const TIMER = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); let duration = typeof a.duration == "undefined" ? tickDuration : a.duration; return new Promise(resolve => - TIMER.initWithCallback(resolve, duration, Ci.nsITimer.TYPE_ONE_SHOT) + timer.initWithCallback(resolve, duration, Ci.nsITimer.TYPE_ONE_SHOT) ); } @@ -1031,8 +1328,21 @@ function flushEvents(container) { } function capitalize(str) { - if (typeof str != "string") { - throw new InvalidArgumentError(`Expected string, got: ${str}`); - } + assert.string(str); return str.charAt(0).toUpperCase() + str.slice(1); } + +function inViewPort(x, y, win) { + assert.number(x, `Expected x to be finite number`); + assert.number(y, `Expected y to be finite number`); + // Viewport includes scrollbars if rendered. + return !(x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight); +} + +function getElementCenter(elementReference, seenEls, container) { + if (element.isWebElementReference(elementReference)) { + let uuid = elementReference[element.Key] || elementReference[element.LegacyKey]; + let el = seenEls.get(uuid, container); + return element.coordinates(el); + } +} diff --git a/testing/marionette/assert.js b/testing/marionette/assert.js index 1c43909fef7e..d1a55bd7cca5 100644 --- a/testing/marionette/assert.js +++ b/testing/marionette/assert.js @@ -100,7 +100,7 @@ assert.b2g = function (msg = "") { assert.content = function (context, msg = "") { msg = msg || "Only supported in content context"; assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context); -} +}; /** * Asserts that the current browser is a mobile browser, that is either @@ -136,6 +136,25 @@ assert.defined = function (obj, msg = "") { return assert.that(o => typeof o != "undefined", msg)(obj); }; +/** + * Asserts that |obj| is a finite number. + * + * @param {?} obj + * Value to test. + * @param {string=} msg + * Custom error message. + * + * @return {number} + * |obj| is returned unaltered. + * + * @throws {InvalidArgumentError} + * If |obj| is not a number. + */ +assert.number = function (obj, msg = "") { + msg = msg || error.pprint`Expected ${obj} to be finite number`; + return assert.that(Number.isFinite, msg)(obj); +}; + /** * Asserts that |obj| is an integer. * @@ -272,7 +291,7 @@ assert.in = function (prop, obj, msg = "") { */ assert.array = function (obj, msg = "") { msg = msg || error.pprint`Expected ${obj} to be an Array`; - return assert.that(o => Array.isArray(o), msg)(obj); + return assert.that(Array.isArray, msg)(obj); }; /** diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index 6c6415e53330..cac9ada1c443 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -944,18 +944,20 @@ GeckoDriver.prototype.get = function*(cmd, resp) { let url = cmd.parameters.url; let get = this.listener.get({url: url, pageTimeout: this.timeouts.pageLoad}); - // TODO(ato): Bug 1242595 - let id = this.listener.activeMessageId; // If a remoteness update interrupts our page load, this will never return // We need to re-issue this request to correctly poll for readyState and // send errors. this.curBrowser.pendingCommands.push(() => { - cmd.parameters.command_id = id; - cmd.parameters.pageTimeout = this.timeouts.pageLoad; + let parameters = { + // TODO(ato): Bug 1242595 + command_id: this.listener.activeMessageId, + pageTimeout: this.timeouts.pageLoad, + startTime: new Date().getTime(), + }; this.mm.broadcastAsyncMessage( "Marionette:pollForReadyState" + this.curBrowser.curFrameId, - cmd.parameters); + parameters); }); yield get; @@ -1018,18 +1020,86 @@ GeckoDriver.prototype.getPageSource = function* (cmd, resp) { } }; -/** Go back in history. */ -GeckoDriver.prototype.goBack = function*(cmd, resp) { +/** + * Cause the browser to traverse one step backward in the joint history + * of the current browsing context. + */ +GeckoDriver.prototype.goBack = function* (cmd, resp) { assert.content(this.context); - yield this.listener.goBack(); + if (!this.curBrowser.tab) { + // Navigation does not work for non-browser windows + return; + } + + let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab) + if (!contentBrowser.webNavigation.canGoBack) { + return; + } + + let currentURL = yield this.listener.getCurrentUrl(); + let goBack = this.listener.goBack({pageTimeout: this.timeouts.pageLoad}); + + // If a remoteness update interrupts our page load, this will never return + // We need to re-issue this request to correctly poll for readyState and + // send errors. + this.curBrowser.pendingCommands.push(() => { + let parameters = { + // TODO(ato): Bug 1242595 + command_id: this.listener.activeMessageId, + lastSeenURL: currentURL, + pageTimeout: this.timeouts.pageLoad, + startTime: new Date().getTime(), + }; + this.mm.broadcastAsyncMessage( + // TODO: combine with + // "Marionette:pollForReadyState" + this.curBrowser.curFrameId, + "Marionette:pollForReadyState" + this.curBrowser.curFrameId, + parameters); + }); + + yield goBack; }; -/** Go forward in history. */ -GeckoDriver.prototype.goForward = function*(cmd, resp) { +/** + * Cause the browser to traverse one step forward in the joint history + * of the current browsing context. + */ +GeckoDriver.prototype.goForward = function* (cmd, resp) { assert.content(this.context); - yield this.listener.goForward(); + if (!this.curBrowser.tab) { + // Navigation does not work for non-browser windows + return; + } + + let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab) + if (!contentBrowser.webNavigation.canGoForward) { + return; + } + + let currentURL = yield this.listener.getCurrentUrl(); + let goForward = this.listener.goForward({pageTimeout: this.timeouts.pageLoad}); + + // If a remoteness update interrupts our page load, this will never return + // We need to re-issue this request to correctly poll for readyState and + // send errors. + this.curBrowser.pendingCommands.push(() => { + let parameters = { + // TODO(ato): Bug 1242595 + command_id: this.listener.activeMessageId, + lastSeenURL: currentURL, + pageTimeout: this.timeouts.pageLoad, + startTime: new Date().getTime(), + }; + this.mm.broadcastAsyncMessage( + // TODO: combine with + // "Marionette:pollForReadyState" + this.curBrowser.curFrameId, + "Marionette:pollForReadyState" + this.curBrowser.curFrameId, + parameters); + }); + + yield goForward; }; /** Refresh the page. */ diff --git a/testing/marionette/error.js b/testing/marionette/error.js index 2b6c2ed7e43a..616af0a8ae46 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -15,6 +15,7 @@ const ERRORS = new Set([ "InvalidSelectorError", "InvalidSessionIDError", "JavaScriptError", + "MoveTargetOutOfBoundsError", "NoAlertOpenError", "NoSuchElementError", "NoSuchFrameError", @@ -301,6 +302,13 @@ class JavaScriptError extends WebDriverError { } } +class MoveTargetOutOfBoundsError extends WebDriverError { + constructor (message) { + super(message); + this.status = "move target out of bounds"; + } +} + class NoAlertOpenError extends WebDriverError { constructor (message) { super(message); @@ -394,6 +402,7 @@ const STATUSES = new Map([ ["invalid selector", InvalidSelectorError], ["invalid session id", InvalidSessionIDError], ["javascript error", JavaScriptError], + ["move target out of bounds", MoveTargetOutOfBoundsError], ["no alert open", NoAlertOpenError], ["no such element", NoSuchElementError], ["no such frame", NoSuchFrameError], diff --git a/testing/marionette/event.js b/testing/marionette/event.js index a02cf772fa91..f2f5565ac298 100644 --- a/testing/marionette/event.js +++ b/testing/marionette/event.js @@ -232,7 +232,7 @@ event.synthesizeMouse = function ( * CSS pixels from the left document margin. * @param {number} top * CSS pixels from the top document margin. - * @param {Object.} event + * @param {Object.} opts * Object which may contain the properties "shiftKey", "ctrlKey", * "altKey", "metaKey", "accessKey", "clickCount", "button", and * "type". @@ -247,15 +247,26 @@ event.synthesizeMouseAtPoint = function ( let button = opts.button || 0; let clickCount = opts.clickCount || 1; let modifiers = event.parseModifiers_(opts); - - if (("type" in event) && opts.type) { + let pressure = ("pressure" in opts) ? opts.pressure : 0; + let inputSource = ("inputSource" in opts) ? opts.inputSource : + Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE; + let isDOMEventSynthesized = + ("isSynthesized" in opts) ? opts.isSynthesized : true; + let isWidgetEventSynthesized = + ("isWidgetEventSynthesized" in opts) ? opts.isWidgetEventSynthesized : false; + let buttons = ("buttons" in opts) ? opts.buttons : domutils.MOUSE_BUTTONS_NOT_SPECIFIED; + + if (("type" in opts) && opts.type) { domutils.sendMouseEvent( - opts.type, left, top, button, clickCount, modifiers); + opts.type, left, top, button, clickCount, modifiers, false, pressure, inputSource, + isDOMEventSynthesized, isWidgetEventSynthesized, buttons); } else { domutils.sendMouseEvent( - "mousedown", left, top, button, clickCount, modifiers); + "mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource, + isDOMEventSynthesized, isWidgetEventSynthesized, buttons); domutils.sendMouseEvent( - "mouseup", left, top, button, clickCount, modifiers); + "mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource, + isDOMEventSynthesized, isWidgetEventSynthesized, buttons); } }; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py index 5d8f28f0a135..e9992f8a562b 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py @@ -44,13 +44,10 @@ def test_back_forward(self): self.marionette.navigate("about:support") self.marionette.go_back() - Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri, - message="'{}' hasn't been loaded".format(self.remote_uri)) + self.assertEqual(self.marionette.get_url(), self.remote_uri) - # Bug 1332998 - Timeout loading the page - # self.marionette.go_forward() - # Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri, - # message="'about:support' hasn't been loaded") + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), "about:support") self.marionette.close() self.marionette.switch_to_window(self.start_tab) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py index a2c0be39de37..38cc02cb8b90 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py @@ -6,9 +6,10 @@ import time import urllib -from marionette_driver import errors, By, Wait +from marionette_driver import By, errors, expected, Wait from marionette_harness import ( MarionetteTestCase, + run_if_e10s, run_if_manage_instance, skip, skip_if_mobile, @@ -20,6 +21,216 @@ def inline(doc): return "data:text/html;charset=utf-8,%s" % urllib.quote(doc) +class TestBackForwardNavigation(WindowManagerMixin, MarionetteTestCase): + + def setUp(self): + super(TestBackForwardNavigation, self).setUp() + + self.test_page = self.marionette.absolute_url('test.html') + + def open_with_link(): + link = self.marionette.find_element(By.ID, "new-blank-tab") + link.click() + + # Always use a blank new tab for an empty history + self.marionette.navigate(self.marionette.absolute_url("windowHandles.html")) + self.new_tab = self.open_tab(open_with_link) + self.marionette.switch_to_window(self.new_tab) + self.assertEqual(self.history_length, 1) + + def tearDown(self): + self.marionette.switch_to_parent_frame() + self.close_all_tabs() + + super(TestBackForwardNavigation, self).tearDown() + + @property + def history_length(self): + return self.marionette.execute_script("return window.history.length;") + + def run_test(self, test_pages): + # Helper method to run simple back and forward testcases. + for index, page in enumerate(test_pages): + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.navigate(page["url"]) + else: + self.marionette.navigate(page["url"]) + self.assertEqual(page["url"], self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + for page in test_pages[-2::-1]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_back() + else: + self.marionette.go_back() + self.assertEqual(page["url"], self.marionette.get_url()) + + for page in test_pages[1::]: + if "error" in page: + with self.assertRaises(page["error"]): + self.marionette.go_forward() + else: + self.marionette.go_forward() + self.assertEqual(page["url"], self.marionette.get_url()) + + def test_no_history_items(self): + # Both methods should not raise a failure if no navigation is possible + self.marionette.go_back() + self.marionette.go_forward() + + def test_data_urls(self): + test_pages = [ + {"url": inline("

foobar

")}, + {"url": self.test_page}, + {"url": inline("

foobar

")}, + ] + self.run_test(test_pages) + + def test_same_document_hash_change(self): + test_pages = [ + {"url": "{}#23".format(self.test_page)}, + {"url": self.test_page}, + {"url": "{}#42".format(self.test_page)}, + ] + self.run_test(test_pages) + + @skip("Causes crashes for JS GC (bug 1344863) and a11y (bug 1344868)") + def test_frameset(self): + test_pages = [ + {"url": self.marionette.absolute_url("frameset.html")}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url("frameset.html")}, + ] + self.run_test(test_pages) + + def test_frameset_after_navigating_in_frame(self): + test_element_locator = (By.ID, "email") + + self.marionette.navigate(self.test_page) + self.assertEqual(self.marionette.get_url(), self.test_page) + self.assertEqual(self.history_length, 1) + page = self.marionette.absolute_url("frameset.html") + self.marionette.navigate(page) + self.assertEqual(self.marionette.get_url(), page) + self.assertEqual(self.history_length, 2) + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + link = self.marionette.find_element(By.ID, "linkId") + link.click() + + # We cannot use get_url() to wait until the target page has been loaded, + # because it will return the URL of the top browsing context and doesn't + # wait for the page load to be complete. + Wait(self.marionette, timeout=self.marionette.timeout.page_load).until( + expected.element_present(*test_element_locator), + message="Target element 'email' has not been found") + self.assertEqual(self.history_length, 3) + + # Go back to the frame the click navigated away from + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), page) + with self.assertRaises(errors.NoSuchElementException): + self.marionette.find_element(*test_element_locator) + + # Go back to the non-frameset page + self.marionette.switch_to_parent_frame() + self.marionette.go_back() + self.assertEqual(self.marionette.get_url(), self.test_page) + + # Go forward to the frameset page + self.marionette.go_forward() + self.assertEqual(self.marionette.get_url(), page) + + # Go forward to the frame the click navigated to + # TODO: See above for automatic browser context switches. Hard to do here + frame = self.marionette.find_element(By.ID, "fifth") + self.marionette.switch_to_frame(frame) + self.marionette.go_forward() + self.marionette.find_element(*test_element_locator) + self.assertEqual(self.marionette.get_url(), page) + + def test_image_document_to_html(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.test_page}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + def test_image_document_to_image_document(self): + test_pages = [ + {"url": self.marionette.absolute_url('black.png')}, + {"url": self.marionette.absolute_url('white.png')}, + ] + self.run_test(test_pages) + + @run_if_e10s("Requires e10s mode enabled") + def test_remoteness_change(self): + # TODO: Verify that a remoteness change happened + # like: self.assertNotEqual(self.marionette.current_window_handle, self.new_tab) + + # about:robots is always a non-remote page for now + test_pages = [ + {"url": "about:robots"}, + {"url": self.test_page}, + {"url": "about:robots"}, + ] + self.run_test(test_pages) + + def test_navigate_to_requested_about_page_after_error_page(self): + test_pages = [ + {"url": "about:neterror"}, + {"url": self.marionette.absolute_url("test.html")}, + {"url": "about:blocked"}, + ] + self.run_test(test_pages) + + def test_timeout_error(self): + urls = [ + self.marionette.absolute_url('slow'), + self.test_page, + self.marionette.absolute_url('slow'), + ] + + # First, load all pages completely to get them added to the cache + for index, url in enumerate(urls): + self.marionette.navigate(url) + self.assertEqual(url, self.marionette.get_url()) + self.assertEqual(self.history_length, index + 1) + + self.marionette.go_back() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_back() + self.assertEqual(urls[0], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + self.marionette.go_forward() + self.assertEqual(urls[1], self.marionette.get_url()) + + # Force triggering a timeout error + self.marionette.timeout.page_load = 0.1 + with self.assertRaises(errors.TimeoutException): + self.marionette.go_forward() + self.assertEqual(urls[2], self.marionette.get_url()) + self.marionette.timeout.page_load = 300000 + + def test_certificate_error(self): + test_pages = [ + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + {"url": self.test_page}, + {"url": self.fixtures.where_is("/test.html", on="https"), + "error": errors.InsecureCertificateException}, + ] + self.run_test(test_pages) + + class TestNavigate(WindowManagerMixin, MarionetteTestCase): def setUp(self): @@ -30,7 +241,8 @@ def setUp(self): self.iframe_doc = self.marionette.absolute_url("test_iframe.html") def tearDown(self): - self.close_all_windows() + self.marionette.timeout.reset() + self.close_all_tabs() super(TestNavigate, self).tearDown() @@ -52,11 +264,6 @@ def test_set_location_through_execute_script(self): lambda _: self.test_doc == self.location_href) self.assertEqual("Marionette Test", self.marionette.title) - def test_navigate(self): - self.marionette.navigate(self.test_doc) - self.assertNotEqual("about:", self.location_href) - self.assertEqual("Marionette Test", self.marionette.title) - def test_navigate_chrome_error(self): with self.marionette.using_context("chrome"): self.assertRaises(errors.UnsupportedOperationException, @@ -78,33 +285,6 @@ def test_get_current_url(self): self.marionette.navigate("about:blank") self.assertEqual("about:blank", self.marionette.get_url()) - # TODO(ato): Remove wait conditions when fixing bug 1330348 - def test_go_back(self): - self.marionette.navigate(self.test_doc) - self.assertNotEqual("about:blank", self.location_href) - self.assertEqual("Marionette Test", self.marionette.title) - self.marionette.navigate("about:blank") - self.assertEqual("about:blank", self.location_href) - self.marionette.go_back() - Wait(self.marionette).until(lambda m: self.location_href == self.test_doc) - self.assertNotEqual("about:blank", self.location_href) - self.assertEqual("Marionette Test", self.marionette.title) - - # TODO(ato): Remove wait conditions when fixing bug 1330348 - def test_go_forward(self): - self.marionette.navigate(self.test_doc) - self.assertNotEqual("about:blank", self.location_href) - self.assertEqual("Marionette Test", self.marionette.title) - self.marionette.navigate("about:blank") - self.assertEqual("about:blank", self.location_href) - self.marionette.go_back() - Wait(self.marionette).until(lambda m: self.location_href == self.test_doc) - self.assertEqual(self.test_doc, self.location_href) - self.assertEqual("Marionette Test", self.marionette.title) - self.marionette.go_forward() - Wait(self.marionette).until(lambda m: self.location_href == "about:blank") - self.assertEqual("about:blank", self.location_href) - def test_refresh(self): self.marionette.navigate(self.test_doc) self.assertEqual("Marionette Test", self.marionette.title) @@ -137,13 +317,6 @@ def test_invalid_protocol(self): with self.assertRaises(errors.MarionetteException): self.marionette.navigate("thisprotocoldoesnotexist://") - def test_should_navigate_to_requested_about_page(self): - self.marionette.navigate("about:neterror") - self.assertEqual(self.marionette.get_url(), "about:neterror") - self.marionette.navigate(self.marionette.absolute_url("test.html")) - self.marionette.navigate("about:blocked") - self.assertEqual(self.marionette.get_url(), "about:blocked") - def test_find_element_state_complete(self): self.marionette.navigate(self.test_doc) state = self.marionette.execute_script( @@ -152,21 +325,21 @@ def test_find_element_state_complete(self): self.assertTrue(self.marionette.find_element(By.ID, "mozLink")) def test_error_when_exceeding_page_load_timeout(self): + self.marionette.timeout.page_load = 0.1 with self.assertRaises(errors.TimeoutException): - self.marionette.timeout.page_load = 0.1 self.marionette.navigate(self.marionette.absolute_url("slow")) - self.marionette.find_element(By.TAG_NAME, "p") - def test_navigate_iframe(self): - self.marionette.navigate(self.iframe_doc) - self.assertTrue('test_iframe.html' in self.marionette.get_url()) - self.assertTrue(self.marionette.find_element(By.ID, "test_iframe")) + def test_navigate_to_same_image_document_twice(self): + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) + self.marionette.navigate(self.fixtures.where_is("black.png")) + self.assertIn("black.png", self.marionette.title) - def test_fragment(self): + def test_navigate_hash_change(self): doc = inline("

") self.marionette.navigate(doc) self.marionette.execute_script("window.visited = true", sandbox=None) - self.marionette.navigate("%s#foo" % doc) + self.marionette.navigate("{}#foo".format(doc)) self.assertTrue(self.marionette.execute_script( "return window.visited", sandbox=None)) @@ -187,23 +360,6 @@ def open_with_link(): self.marionette.close() self.marionette.switch_to_window(self.start_window) - def test_error_on_tls_navigation(self): - self.assertRaises(errors.InsecureCertificateException, - self.marionette.navigate, self.fixtures.where_is("/test.html", on="https")) - - def test_html_document_to_image_document(self): - self.marionette.navigate(self.fixtures.where_is("test.html")) - self.marionette.navigate(self.fixtures.where_is("white.png")) - self.assertIn("white.png", self.marionette.title) - - def test_image_document_to_image_document(self): - self.marionette.navigate(self.fixtures.where_is("test.html")) - - self.marionette.navigate(self.fixtures.where_is("white.png")) - self.assertIn("white.png", self.marionette.title) - self.marionette.navigate(self.fixtures.where_is("black.png")) - self.assertIn("black.png", self.marionette.title) - @run_if_manage_instance("Only runnable if Marionette manages the instance") @skip_if_mobile("Bug 1322993 - Missing temporary folder") def test_focus_after_navigation(self): diff --git a/testing/marionette/harness/marionette_harness/www/frameset.html b/testing/marionette/harness/marionette_harness/www/frameset.html index 1136b35b3994..209d705b5ab6 100644 --- a/testing/marionette/harness/marionette_harness/www/frameset.html +++ b/testing/marionette/harness/marionette_harness/www/frameset.html @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/testing/marionette/harness/marionette_harness/www/xhtmlTest.html b/testing/marionette/harness/marionette_harness/www/xhtmlTest.html index e6e7926bdb99..146def33ad0e 100644 --- a/testing/marionette/harness/marionette_harness/www/xhtmlTest.html +++ b/testing/marionette/harness/marionette_harness/www/xhtmlTest.html @@ -10,7 +10,7 @@

diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index a051bccd3ba9..65c491100a57 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -222,7 +222,6 @@ var getTitleFn = dispatch(getTitle); var getPageSourceFn = dispatch(getPageSource); var getActiveElementFn = dispatch(getActiveElement); var clickElementFn = dispatch(clickElement); -var goBackFn = dispatch(goBack); var getElementAttributeFn = dispatch(getElementAttribute); var getElementPropertyFn = dispatch(getElementProperty); var getElementTextFn = dispatch(getElementText); @@ -271,7 +270,7 @@ function startListeners() { addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn); addMessageListenerId("Marionette:getTitle", getTitleFn); addMessageListenerId("Marionette:getPageSource", getPageSourceFn); - addMessageListenerId("Marionette:goBack", goBackFn); + addMessageListenerId("Marionette:goBack", goBack); addMessageListenerId("Marionette:goForward", goForward); addMessageListenerId("Marionette:refresh", refresh); addMessageListenerId("Marionette:findElementContent", findElementContentFn); @@ -376,7 +375,7 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:getTitle", getTitleFn); removeMessageListenerId("Marionette:getPageSource", getPageSourceFn); removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn); - removeMessageListenerId("Marionette:goBack", goBackFn); + removeMessageListenerId("Marionette:goBack", goBack); removeMessageListenerId("Marionette:goForward", goForward); removeMessageListenerId("Marionette:refresh", refresh); removeMessageListenerId("Marionette:findElementContent", findElementContentFn); @@ -685,9 +684,9 @@ function createATouch(el, corx, cory, touchId) { * Object with an |actions| attribute that is an Array of objects * each of which represents an action sequence. */ -function performActions(msg) { +function* performActions(msg) { let chain = action.Chain.fromJson(msg.actions); - action.dispatch(chain, seenEls, curContainer); + yield action.dispatch(chain, seenEls, curContainer); } /** @@ -696,8 +695,8 @@ function performActions(msg) { * the state was released by an explicit series of actions. It also clears all * the internal state of the virtual devices. */ -function releaseActions() { - action.dispatchTickActions(action.inputsToCancel.reverse(), 0, seenEls, curContainer); +function* releaseActions() { + yield action.dispatchTickActions(action.inputsToCancel.reverse(), 0, seenEls, curContainer); action.inputsToCancel.length = 0; action.inputStateMap.clear(); } @@ -886,46 +885,70 @@ function multiAction(args, maxLen) { setDispatch(concurrentEvent, pendingTouches); } -/* +/** * This implements the latter part of a get request (for the case we need to resume one * when a remoteness update happens in the middle of a navigate request). This is most of * of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire. + * + * @param {function=} cleanupCallback + * Callback to execute when registered event handlers or observer notifications + * have to be cleaned-up. + * @param {number} command_id + * ID of the currently handled message between the driver and listener. + * @param {string=} lastSeenURL + * Last URL as seen before the navigation request got triggered. + * @param {number} pageTimeout + * Timeout in seconds the method has to wait for the page being finished loading. + * @param {number} startTime + * Unix timestap when the navitation request got triggred. */ -function pollForReadyState(msg, start = undefined, callback = undefined) { - let {pageTimeout, url, command_id} = msg.json; - if (!start) { - start = new Date().getTime(); +function pollForReadyState(msg) { + let {cleanupCallback, command_id, lastSeenURL, pageTimeout, startTime} = msg.json; + + if (typeof startTime == "undefined") { + startTime = new Date().getTime(); } - if (!callback) { - callback = () => {}; + + if (typeof cleanupCallback == "undefined") { + cleanupCallback = () => {}; } + let endTime = startTime + pageTimeout; + let checkLoad = function() { navTimer.cancel(); let doc = curContainer.frame.document; - let now = new Date().getTime(); - if (pageTimeout == null || (now - start) <= pageTimeout) { + + if (pageTimeout === null || new Date().getTime() <= endTime) { + // Under some conditions (eg. for error pages) the pagehide event is fired + // even with a readyState complete for the formerly loaded page. + // To prevent race conditition for goBack and goForward we have to wait + // until the last seen page has been fully unloaded. + // TODO: Bug 1333458 has to improve this. + if (!doc.location || lastSeenURL && doc.location.href === lastSeenURL) { + navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); + // document fully loaded - if (doc.readyState == "complete") { - callback(); + } else if (doc.readyState === "complete") { + cleanupCallback(); sendOk(command_id); // document with an insecure cert - } else if (doc.readyState == "interactive" && + } else if (doc.readyState === "interactive" && doc.baseURI.startsWith("about:certerror")) { - callback(); + cleanupCallback(); sendError(new InsecureCertificateError(), command_id); // we have reached an error url without requesting it - } else if (doc.readyState == "interactive" && + } else if (doc.readyState === "interactive" && /about:.+(error)\?/.exec(doc.baseURI)) { - callback(); + cleanupCallback(); sendError(new UnknownError("Reached error page: " + doc.baseURI), command_id); // return early for about: urls - } else if (doc.readyState == "interactive" && doc.baseURI.startsWith("about:")) { - callback(); + } else if (doc.readyState === "interactive" && doc.baseURI.startsWith("about:")) { + cleanupCallback(); sendOk(command_id); // document not fully loaded @@ -934,10 +957,11 @@ function pollForReadyState(msg, start = undefined, callback = undefined) { } } else { - callback(); + cleanupCallback(); sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id); } }; + checkLoad(); } @@ -948,9 +972,10 @@ function pollForReadyState(msg, start = undefined, callback = undefined) { * driver (in chrome space). */ function get(msg) { - let start = new Date().getTime(); let {pageTimeout, url, command_id} = msg.json; + let startTime = new Date().getTime(); + // We need to move to the top frame before navigating sendSyncMessage("Marionette:switchedToFrame", {frameValue: null}); curContainer.frame = content; @@ -1018,9 +1043,15 @@ function get(msg) { // not triggered, which is the case for image documents. else if (state & Ci.nsIWebProgressListener.STATE_STOP && content.document instanceof content.ImageDocument) { - pollForReadyState(msg, start, () => { - removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); - }); + pollForReadyState({json: { + command_id: command_id, + pageTimeout: pageTimeout, + startTime: startTime, + cleanupCallback: () => { + webProgress.removeProgressListener(loadListener); + removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); + } + }}); } }, @@ -1057,10 +1088,15 @@ function get(msg) { docShell.hasLoadedNonBlankURI; if (correctFrame && sawLoad && loadedRequestedURI) { - webProgress.removeProgressListener(loadListener); - pollForReadyState(msg, start, () => { - removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); - }); + pollForReadyState({json: { + command_id: command_id, + pageTimeout: pageTimeout, + startTime: startTime, + cleanupCallback: () => { + webProgress.removeProgressListener(loadListener); + removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); + } + }}); } }; @@ -1099,12 +1135,8 @@ function cancelRequest() { /** * Get URL of the top-level browsing context. */ -function getCurrentUrl(isB2G) { - if (isB2G) { - return curContainer.frame.location.href; - } else { - return content.location.href; - } +function getCurrentUrl() { + return content.location.href; } /** @@ -1121,20 +1153,115 @@ function getPageSource() { return curContainer.frame.document.documentElement.outerHTML; } +/** + * Wait for the current page to be unloaded after a navigation got triggered. + * + * @param {function} trigger + * Callback to execute which triggers a page navigation. + * @param {function} doneCallback + * Callback to execute when the current page has been unloaded. + * + * It receives a dictionary with the following items as argument: + * loading - Flag if a page load will follow. + * lastSeenURL - Last seen URL before the navigation request. + * startTime - Time when the navigation request has been triggered. + */ +function waitForPageUnloaded(trigger, doneCallback) { + let currentURL = curContainer.frame.location.href; + let start = new Date().getTime(); + + function handleEvent(event) { + // In case of a remoteness change it can happen that we are no longer able + // to access the document's location. In those cases ignore the event, + // but keep the code waiting, and assume in the driver that waiting for the + // page load is necessary. Bug 1333458 should improve things. + if (typeof event.originalTarget.location == "undefined") { + return; + } + + switch (event.type) { + case "hashchange": + removeEventListener("hashchange", handleEvent); + removeEventListener("pagehide", handleEvent); + removeEventListener("unload", handleEvent); + + doneCallback({loading: false, lastSeenURL: currentURL}); + break; + + case "pagehide": + case "unload": + if (event.originalTarget === curContainer.frame.document) { + removeEventListener("hashchange", handleEvent); + removeEventListener("pagehide", handleEvent); + removeEventListener("unload", handleEvent); + + doneCallback({loading: true, lastSeenURL: currentURL, startTime: start}); + } + break; + } + } + + addEventListener("hashchange", handleEvent, false); + addEventListener("pagehide", handleEvent, false); + addEventListener("unload", handleEvent, false); + + trigger(); +} + /** * Cause the browser to traverse one step backward in the joint history - * of the current top-level browsing context. + * of the current browsing context. + * + * @param {number} command_id + * ID of the currently handled message between the driver and listener. + * @param {number} pageTimeout + * Timeout in milliseconds the method has to wait for the page being finished loading. */ -function goBack() { - curContainer.frame.history.back(); +function goBack(msg) { + let {command_id, pageTimeout} = msg.json; + + waitForPageUnloaded(() => { + curContainer.frame.history.back(); + }, pageLoadStatus => { + if (pageLoadStatus.loading) { + pollForReadyState({json: { + command_id: command_id, + lastSeenURL: pageLoadStatus.lastSeenURL, + pageTimeout: pageTimeout, + startTime: pageLoadStatus.startTime, + }}); + } else { + sendOk(command_id); + } + }); } /** - * Go forward in history + * Cause the browser to traverse one step forward in the joint history + * of the current browsing context. + * + * @param {number} command_id + * ID of the currently handled message between the driver and listener. + * @param {number} pageTimeout + * Timeout in milliseconds the method has to wait for the page being finished loading. */ function goForward(msg) { - curContainer.frame.history.forward(); - sendOk(msg.json.command_id); + let {command_id, pageTimeout} = msg.json; + + waitForPageUnloaded(() => { + curContainer.frame.history.forward(); + }, pageLoadStatus => { + if (pageLoadStatus.loading) { + pollForReadyState({json: { + command_id: command_id, + lastSeenURL: pageLoadStatus.lastSeenURL, + pageTimeout: pageTimeout, + startTime: pageLoadStatus.startTime, + }}); + } else { + sendOk(command_id); + } + }); } /** diff --git a/testing/marionette/test_action.js b/testing/marionette/test_action.js index 74b0446b26a3..1c58ced766a2 100644 --- a/testing/marionette/test_action.js +++ b/testing/marionette/test_action.js @@ -25,7 +25,7 @@ add_test(function test_createAction() { }); add_test(function test_defaultPointerParameters() { - let defaultParameters = {pointerType: action.PointerType.Mouse, primary: true}; + let defaultParameters = {pointerType: action.PointerType.Mouse}; deepEqual(action.PointerParameters.fromJson(), defaultParameters); run_next_test(); @@ -34,16 +34,15 @@ add_test(function test_defaultPointerParameters() { add_test(function test_processPointerParameters() { let check = (regex, message, arg) => checkErrors( regex, action.PointerParameters.fromJson, [arg], message); - let parametersData = {pointerType: "foo", primary: undefined}; - let message = `parametersData: [pointerType: ${parametersData.pointerType}, ` + - `primary: ${parametersData.primary}]`; - check(/Unknown pointerType/, message, parametersData); - parametersData.pointerType = "pen"; - parametersData.primary = "a"; - check(/Expected \[object String\] "a" to be boolean/, message, parametersData); - parametersData.primary = false; + let parametersData; + for (let d of ["foo", "", "get", "Get"]) { + parametersData = {pointerType: d}; + let message = `parametersData: [pointerType: ${parametersData.pointerType}]`; + check(/Unknown pointerType/, message, parametersData); + } + parametersData.pointerType = "mouse"; //TODO "pen"; deepEqual(action.PointerParameters.fromJson(parametersData), - {pointerType: action.PointerType.Pen, primary: false}); + {pointerType: "mouse"}); //TODO action.PointerType.Pen}); run_next_test(); }); @@ -80,29 +79,61 @@ add_test(function test_validateActionDurationAndCoordinates() { check("pointer", "pointerMove"); } actionItem.duration = 5000; - for (let d of [-1, "a"]) { - for (let name of ["x", "y"]) { - actionItem[name] = d; - check("pointer", "pointerMove", `${name}: ${actionItem[name]}`); - } + for (let name of ["x", "y"]) { + actionItem[name] = "a"; + actionItem.type = "pointerMove"; + actionSequence.type = "pointer"; + checkErrors(/Expected '.*' \(.*\) to be an Integer/, + action.Action.fromJson, [actionSequence, actionItem], + `duration: ${actionItem.duration}, subtype: pointerMove`); } run_next_test(); }); -add_test(function test_processPointerMoveActionElementValidation() { +add_test(function test_processPointerMoveActionOriginValidation() { let actionSequence = {type: "pointer", id: "some_id"}; let actionItem = {duration: 5000, type: "pointerMove"}; - for (let d of [-1, "a", {a: "blah"}]) { - actionItem.element = d; - checkErrors(/Expected 'actionItem.element' to be a web element reference/, + for (let d of [-1, {a: "blah"}, []]) { + actionItem.origin = d; + + checkErrors(/Expected \'origin\' to be a string or a web element reference/, action.Action.fromJson, [actionSequence, actionItem], - `actionItem.element: (${getTypeString(d)})`); + `actionItem.origin: (${getTypeString(d)})`); } - actionItem.element = {[element.Key]: "something"}; + + run_next_test(); +}); + +add_test(function test_processPointerMoveActionOriginStringValidation() { + let actionSequence = {type: "pointer", id: "some_id"}; + let actionItem = {duration: 5000, type: "pointerMove"}; + for (let d of ["a", "", "get", "Get"]) { + actionItem.origin = d; + checkErrors(/Unknown pointer-move origin/, + action.Action.fromJson, + [actionSequence, actionItem], + `actionItem.origin: ${d}`); + } + + run_next_test(); +}); + +add_test(function test_processPointerMoveActionElementOrigin() { + let actionSequence = {type: "pointer", id: "some_id"}; + let actionItem = {duration: 5000, type: "pointerMove"}; + actionItem.origin = {[element.Key]: "something"}; let a = action.Action.fromJson(actionSequence, actionItem); - deepEqual(a.element, actionItem.element); + deepEqual(a.origin, actionItem.origin); + run_next_test(); +}); +add_test(function test_processPointerMoveActionDefaultOrigin() { + let actionSequence = {type: "pointer", id: "some_id"}; + // origin left undefined + let actionItem = {duration: 5000, type: "pointerMove"}; + let a = action.Action.fromJson(actionSequence, actionItem); + deepEqual(a.origin, action.PointerOrigin.Viewport); run_next_test(); }); @@ -112,14 +143,14 @@ add_test(function test_processPointerMoveAction() { { duration: 5000, type: "pointerMove", - element: undefined, + origin: undefined, x: undefined, y: undefined, }, { duration: undefined, type: "pointerMove", - element: {[element.Key]: "id", [element.LegacyKey]: "id"}, + origin: {[element.Key]: "id", [element.LegacyKey]: "id"}, x: undefined, y: undefined, }, @@ -128,34 +159,88 @@ add_test(function test_processPointerMoveAction() { type: "pointerMove", x: 0, y: undefined, - element: undefined, + origin: undefined, }, { duration: 5000, type: "pointerMove", x: 1, y: 2, - element: undefined, + origin: undefined, }, ]; for (let expected of actionItems) { let actual = action.Action.fromJson(actionSequence, expected); ok(actual instanceof action.Action); equal(actual.duration, expected.duration); - equal(actual.element, expected.element); equal(actual.x, expected.x); equal(actual.y, expected.y); + + let origin = expected.origin; + if (typeof origin == "undefined") { + origin = action.PointerOrigin.Viewport; + } + deepEqual(actual.origin, origin); + } run_next_test(); }); +add_test(function test_computePointerDestinationViewport() { + let act = { type: "pointerMove", x: 100, y: 200, origin: "viewport"}; + let inputState = new action.InputState.Pointer(action.PointerType.Mouse); + // these values should not affect the outcome + inputState.x = "99"; + inputState.y = "10"; + let target = action.computePointerDestination(act, inputState); + equal(act.x, target.x); + equal(act.y, target.y); + + run_next_test(); +}); + +add_test(function test_computePointerDestinationPointer() { + let act = { type: "pointerMove", x: 100, y: 200, origin: "pointer"}; + let inputState = new action.InputState.Pointer(action.PointerType.Mouse); + inputState.x = 10; + inputState.y = 99; + let target = action.computePointerDestination(act, inputState); + equal(act.x + inputState.x, target.x); + equal(act.y + inputState.y, target.y); + + + run_next_test(); +}); + +add_test(function test_computePointerDestinationElement() { + // origin represents a web element + // using an object literal instead to test default case in computePointerDestination + let act = {type: "pointerMove", x: 100, y: 200, origin: {}}; + let inputState = new action.InputState.Pointer(action.PointerType.Mouse); + let elementCenter = {x: 10, y: 99}; + let target = action.computePointerDestination(act, inputState, elementCenter); + equal(act.x + elementCenter.x, target.x); + equal(act.y + elementCenter.y, target.y); + + Assert.throws( + () => action.computePointerDestination(act, inputState, {a: 1}), + InvalidArgumentError, + "Invalid element center coordinates."); + + Assert.throws( + () => action.computePointerDestination(act, inputState, undefined), + InvalidArgumentError, + "Undefined element center coordinates."); + + run_next_test(); +}); + add_test(function test_processPointerAction() { let actionSequence = { type: "pointer", id: "some_id", parameters: { - pointerType: "touch", - primary: false, + pointerType: "mouse" //TODO "touch" }, }; let actionItems = [ @@ -183,7 +268,6 @@ add_test(function test_processPointerAction() { equal(actual.duration, expected.duration); } if (expected.type !== "pause") { - equal(actual.primary, actionSequence.parameters.primary); equal(actual.pointerType, actionSequence.parameters.pointerType); } } @@ -253,20 +337,24 @@ add_test(function test_processInputSourceActionSequenceValidation() { let check = (message, regex) => checkErrors( regex, action.Sequence.fromJson, [actionSequence], message); check(`actionSequence.type: ${actionSequence.type}`, /Unknown action type/); + action.inputStateMap.clear(); actionSequence.type = "none"; actionSequence.id = -1; check(`actionSequence.id: ${getTypeString(actionSequence.id)}`, /Expected 'id' to be a string/); + action.inputStateMap.clear(); actionSequence.id = undefined; check(`actionSequence.id: ${getTypeString(actionSequence.id)}`, /Expected 'id' to be defined/); + action.inputStateMap.clear(); actionSequence.id = "some_id"; actionSequence.actions = -1; check(`actionSequence.actions: ${getTypeString(actionSequence.actions)}`, /Expected 'actionSequence.actions' to be an Array/); + action.inputStateMap.clear(); run_next_test(); }); @@ -283,6 +371,7 @@ add_test(function test_processInputSourceActionSequence() { let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); + action.inputStateMap.clear(); run_next_test(); }); @@ -293,18 +382,17 @@ add_test(function test_processInputSourceActionSequencePointer() { id: "9", actions: [actionItem], parameters: { - pointerType: "pen", - primary: false, + pointerType: "mouse" // TODO "pen" }, }; let expectedAction = new action.Action( actionSequence.id, actionSequence.type, actionItem.type); expectedAction.pointerType = actionSequence.parameters.pointerType; - expectedAction.primary = actionSequence.parameters.primary; expectedAction.button = actionItem.button; let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); + action.inputStateMap.clear(); run_next_test(); }); @@ -321,6 +409,7 @@ add_test(function test_processInputSourceActionSequenceKey() { let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); + action.inputStateMap.clear(); run_next_test(); }); @@ -349,26 +438,40 @@ add_test(function test_processInputSourceActionSequenceInputStateMap() { add_test(function test_processPointerActionInputStateMap() { let actionItem = {type: "pointerDown"}; let id = "1"; - let parameters = {pointerType: "mouse", primary: false}; + let parameters = {pointerType: "mouse"}; let a = new action.Action(id, "pointer", actionItem.type); - let wrongInputState = new action.InputState.Pointer("pause", true); + let wrongInputState = new action.InputState.Key(); action.inputStateMap.set(id, wrongInputState); checkErrors( - /to be mapped to InputState whose subtype is/, action.processPointerAction, + /to be mapped to InputState whose type is/, action.processPointerAction, [id, parameters, a], - `$subtype ${actionItem.type} with ${wrongInputState.subtype} in inputState`); + `type "pointer" with ${wrongInputState.type} in inputState`); action.inputStateMap.clear(); - let rightInputState = new action.InputState.Pointer("pointerDown", false); + + // TODO - uncomment once pen is supported + //wrongInputState = new action.InputState.Pointer("pen"); + //action.inputStateMap.set(id, wrongInputState); + //checkErrors( + // /to be mapped to InputState whose subtype is/, action.processPointerAction, + // [id, parameters, a], + // `subtype ${parameters.pointerType} with ${wrongInputState.subtype} in inputState`); + //action.inputStateMap.clear(); + + let rightInputState = new action.InputState.Pointer("mouse"); action.inputStateMap.set(id, rightInputState); action.processPointerAction(id, parameters, a); - equal(a.primary, parameters.primary); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_createInputState() { for (let kind in action.InputState) { - let state = new action.InputState[kind](); + let state; + if (kind == "Pointer") { + state = new action.InputState[kind]("mouse"); + } else { + state = new action.InputState[kind](); + } ok(state); if (kind === "Null") { equal(state.type, "none"); @@ -376,6 +479,10 @@ add_test(function test_createInputState() { equal(state.type, kind.toLowerCase()); } } + Assert.throws(() => new action.InputState.Pointer(), InvalidArgumentError, + "Missing InputState.Pointer constructor arg"); + Assert.throws(() => new action.InputState.Pointer("foo"), InvalidArgumentError, + "Invalid InputState.Pointer constructor arg"); run_next_test(); }); @@ -408,6 +515,7 @@ add_test(function test_extractActionChain_oneTickOneInput() { equal(1, actionsByTick.length); equal(1, actionsByTick[0].length); deepEqual(actionsByTick, [[expectedAction]]); + action.inputStateMap.clear(); run_next_test(); }); @@ -427,8 +535,7 @@ add_test(function test_extractActionChain_twoAndThreeTicks() { id: "7", actions: mouseActionItems, parameters: { - pointerType: "touch", - primary: false, + pointerType: "mouse" //TODO "touch" }, }; let keyActionItems = [ @@ -459,12 +566,14 @@ add_test(function test_extractActionChain_twoAndThreeTicks() { let expectedAction = new action.Action(keyActionSequence.id, "key", keyActionItems[2].type); expectedAction.value = keyActionItems[2].value; deepEqual(actionsByTick[2][0], expectedAction); + action.inputStateMap.clear(); // one empty action sequence actionsByTick = action.Chain.fromJson( [keyActionSequence, {type: "none", id: "some", actions: []}]); equal(keyActionItems.length, actionsByTick.length); equal(1, actionsByTick[0].length); + action.inputStateMap.clear(); run_next_test(); }); diff --git a/testing/marionette/test_assert.js b/testing/marionette/test_assert.js index 1d6cd1e9443b..c14f2852e338 100644 --- a/testing/marionette/test_assert.js +++ b/testing/marionette/test_assert.js @@ -40,11 +40,23 @@ add_test(function test_defined() { run_next_test(); }); +add_test(function test_number() { + assert.number(1); + assert.number(0); + assert.number(-1); + assert.number(1.2); + for (let i of ["foo", "1", {}, [], NaN, Infinity, undefined]) { + Assert.throws(() => assert.number(i), InvalidArgumentError); + } + run_next_test(); +}); + add_test(function test_integer() { assert.integer(1); assert.integer(0); assert.integer(-1); Assert.throws(() => assert.integer("foo"), InvalidArgumentError); + Assert.throws(() => assert.integer(1.2), InvalidArgumentError); run_next_test(); }); diff --git a/testing/mozharness/configs/beetmover/en_us.yml.tmpl b/testing/mozharness/configs/beetmover/en_us_build.yml.tmpl similarity index 68% rename from testing/mozharness/configs/beetmover/en_us.yml.tmpl rename to testing/mozharness/configs/beetmover/en_us_build.yml.tmpl index cbef7f3000fb..33287b0426d6 100644 --- a/testing/mozharness/configs/beetmover/en_us.yml.tmpl +++ b/testing/mozharness/configs/beetmover/en_us_build.yml.tmpl @@ -6,17 +6,9 @@ metadata: mapping: {% for locale in locales %} - # common deliverables {{ locale }}: - complete_mar: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar - s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar - checksum: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums - checksum_sig: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums.asc - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums.asc + + {% if platform == "win32" %} buildinfo: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json @@ -35,19 +27,6 @@ mapping: xpi: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi - - {% if platform == "win32" %} - full_installer: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe - {% if "esr" not in version %} - stub_installer: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer-stub.exe - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup Stub {{ version }}.exe - {% endif %} - package: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip symbols: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip @@ -66,12 +45,24 @@ mapping: {% endif %} {% if platform == "win64" %} - full_installer: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe - package: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip + buildinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json + mozinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json + socorroinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt + jsshell: + artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip + s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip + mozharness_package: + artifact: {{ artifact_base_url }}/mozharness.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip + xpi: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi + s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi symbols: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip @@ -90,9 +81,24 @@ mapping: {% endif %} {% if platform == "linux-i686" %} - package: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2 - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2 + buildinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json + mozinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json + socorroinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt + jsshell: + artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip + s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip + mozharness_package: + artifact: {{ artifact_base_url }}/mozharness.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip + xpi: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi + s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi symbols: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip @@ -111,9 +117,24 @@ mapping: {% endif %} {% if platform == "linux-x86_64" %} - package: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2 - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2 + buildinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json + mozinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json + socorroinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt + jsshell: + artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip + s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip + mozharness_package: + artifact: {{ artifact_base_url }}/mozharness.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip + xpi: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi + s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi symbols: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip @@ -132,9 +153,24 @@ mapping: {% endif %} {% if platform == "mac" %} - package: - artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.dmg - s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg + buildinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json + mozinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json + socorroinfo: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt + jsshell: + artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip + s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip + mozharness_package: + artifact: {{ artifact_base_url }}/mozharness.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip + xpi: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi + s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi symbols: artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.crashreporter-symbols.zip diff --git a/testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl b/testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl new file mode 100644 index 000000000000..54fc2c792b3e --- /dev/null +++ b/testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl @@ -0,0 +1,66 @@ +--- +metadata: + name: "Beet Mover Manifest" + description: "Maps artifact locations to s3 key names for the en-US locale" + owner: "release@mozilla.com" + +mapping: +{% for locale in locales %} + {{ locale }}: + {% if platform == "win32" %} + complete_mar: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar + s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar + full_installer: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe + {% if "esr" not in version %} + stub_installer: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer-stub.exe + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup Stub {{ version }}.exe + {% endif %} + package: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip + {% endif %} + + {% if platform == "win64" %} + complete_mar: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar + s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar + full_installer: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe + package: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip + {% endif %} + + {% if platform == "linux-i686" %} + complete_mar: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar + s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar + package: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2 + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2 + {% endif %} + + {% if platform == "linux-x86_64" %} + complete_mar: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar + s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar + package: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2 + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2 + {% endif %} + + {% if platform == "mac" %} + complete_mar: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar + s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar + package: + artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.dmg + s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg + {% endif %} + +{% endfor %} diff --git a/testing/mozharness/configs/releases/updates_firefox_release.py b/testing/mozharness/configs/releases/updates_firefox_release.py index b9a21e6216c9..a4cd73498581 100644 --- a/testing/mozharness/configs/releases/updates_firefox_release.py +++ b/testing/mozharness/configs/releases/updates_firefox_release.py @@ -40,7 +40,7 @@ "mar_channel_ids": [], "channel_names": ["release", "release-localtest", "release-cdntest"], "rules_to_update": ["firefox-release-cdntest", "firefox-release-localtest"], - "publish_rules": ["firefox-release"], + "publish_rules": ["firefox-release", "firefox-release-nowebsense-bypass"], }, }, "balrog_use_dummy_suffix": False, diff --git a/testing/talos/talos/xtalos/xperf_whitelist.json b/testing/talos/talos/xtalos/xperf_whitelist.json index 7f78f08f473f..96ae959f6ea5 100644 --- a/testing/talos/talos/xtalos/xperf_whitelist.json +++ b/testing/talos/talos/xtalos/xperf_whitelist.json @@ -7,6 +7,7 @@ "{firefox}\\omni.ja": {"mincount": 0, "maxcount": 46, "minbytes": 0, "maxbytes": 3014656}, "{firefox}\\browser\\omni.ja": {"mincount": 0, "maxcount": 28, "minbytes": 0, "maxbytes": 1835008}, "{firefox}\\browser\\features\\aushelper@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000}, + "{firefox}\\browser\\features\\deployment-checker@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000}, "{firefox}\\browser\\features\\e10srollout@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000}, "{firefox}\\browser\\features\\flyweb@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000}, "{firefox}\\browser\\features\\formautofill@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000}, diff --git a/testing/web-platform/harness/wptrunner/executors/executormarionette.py b/testing/web-platform/harness/wptrunner/executors/executormarionette.py index d63fe0a4b05d..e212c2945484 100644 --- a/testing/web-platform/harness/wptrunner/executors/executormarionette.py +++ b/testing/web-platform/harness/wptrunner/executors/executormarionette.py @@ -590,5 +590,6 @@ def do_test(self, test): def do_wdspec(self, session, path, timeout): harness_result = ("OK", None) - subtest_results = pytestrunner.run(path, session, timeout=timeout) + subtest_results = pytestrunner.run( + path, session, self.server_url, timeout=timeout) return (harness_result, subtest_results) diff --git a/testing/web-platform/harness/wptrunner/executors/pytestrunner/fixtures.py b/testing/web-platform/harness/wptrunner/executors/pytestrunner/fixtures.py index 77afb4a36842..1b4e8d43d7a3 100644 --- a/testing/web-platform/harness/wptrunner/executors/pytestrunner/fixtures.py +++ b/testing/web-platform/harness/wptrunner/executors/pytestrunner/fixtures.py @@ -4,6 +4,8 @@ import pytest +import urlparse + """pytest fixtures for use in Python-based WPT tests. @@ -56,3 +58,19 @@ def __init__(self, client): def session(self, request): request.addfinalizer(self.client.end) return self.client + +class Server(object): + """Fixture to allow access to wptrunner's base server url. + + :param url_getter: Function to get server url from test environment, given + a protocol. + """ + def __init__(self, url_getter): + self.server_url = url_getter + + def where_is(self, uri, protocol="http"): + return urlparse.urljoin(self.server_url(protocol), uri) + + @pytest.fixture + def server(self, request): + return self diff --git a/testing/web-platform/harness/wptrunner/executors/pytestrunner/runner.py b/testing/web-platform/harness/wptrunner/executors/pytestrunner/runner.py index 8aa575ff8b7f..28b8f609cbf1 100644 --- a/testing/web-platform/harness/wptrunner/executors/pytestrunner/runner.py +++ b/testing/web-platform/harness/wptrunner/executors/pytestrunner/runner.py @@ -27,12 +27,14 @@ def do_delayed_imports(): import pytest -def run(path, session, timeout=0): +def run(path, session, url_getter, timeout=0): """Run Python test at ``path`` in pytest. The provided ``session`` is exposed as a fixture available in the scope of the test functions. :param path: Path to the test file. :param session: WebDriver session to expose. + :param url_getter: Function to get server url from test environment, given + a protocol. :param timeout: Duration before interrupting potentially hanging tests. If 0, there is no timeout. @@ -45,7 +47,8 @@ def run(path, session, timeout=0): recorder = SubtestResultRecorder() plugins = [recorder, - fixtures.Session(session)] + fixtures.Session(session), + fixtures.Server(url_getter)] # TODO(ato): Deal with timeouts diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index 806867cbe0ae..65626d6b8cba 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -38758,6 +38758,32 @@ "url": "/webaudio/the-audio-api/the-constantsourcenode-interface/test-constantsourcenode.html" } ] + }, + "wdspec": { + "webdriver/actions/conftest.py": [ + { + "path": "webdriver/actions/conftest.py", + "url": "/webdriver/actions/conftest.py" + } + ], + "webdriver/actions/key.py": [ + { + "path": "webdriver/actions/key.py", + "url": "/webdriver/actions/key.py" + } + ], + "webdriver/actions/mouse.py": [ + { + "path": "webdriver/actions/mouse.py", + "url": "/webdriver/actions/mouse.py" + } + ], + "webdriver/actions/sequence.py": [ + { + "path": "webdriver/actions/sequence.py", + "url": "/webdriver/actions/sequence.py" + } + ] } }, "reftest_nodes": { diff --git a/testing/web-platform/meta/webdriver/actions/key.py.ini b/testing/web-platform/meta/webdriver/actions/key.py.ini new file mode 100644 index 000000000000..d773c233eed6 --- /dev/null +++ b/testing/web-platform/meta/webdriver/actions/key.py.ini @@ -0,0 +1,4 @@ +[key.py] + type: wdspec + disabled: + if (os == "linux") and (bits == 64) and debug: https://bugzilla.mozilla.org/show_bug.cgi?id=1318724 diff --git a/testing/web-platform/meta/webdriver/actions/mouse.py.ini b/testing/web-platform/meta/webdriver/actions/mouse.py.ini new file mode 100644 index 000000000000..45918bc213f5 --- /dev/null +++ b/testing/web-platform/meta/webdriver/actions/mouse.py.ini @@ -0,0 +1,6 @@ +[mouse.py] + type: wdspec + disabled: + if (os == "linux") and (bits == 64) and debug: https://bugzilla.mozilla.org/show_bug.cgi?id=1318724 + [mouse.py::test_click_at_coordinates] + expected: ERROR diff --git a/testing/web-platform/meta/webdriver/actions/sequence.py.ini b/testing/web-platform/meta/webdriver/actions/sequence.py.ini new file mode 100644 index 000000000000..9c80c29b796e --- /dev/null +++ b/testing/web-platform/meta/webdriver/actions/sequence.py.ini @@ -0,0 +1,3 @@ +[sequence.py] + type: wdspec + disabled: true diff --git a/testing/web-platform/meta/webdriver/contexts.py.ini b/testing/web-platform/meta/webdriver/contexts.py.ini index 6385cb8d4ba0..712259367ee1 100644 --- a/testing/web-platform/meta/webdriver/contexts.py.ini +++ b/testing/web-platform/meta/webdriver/contexts.py.ini @@ -1,12 +1,9 @@ [contexts.py] type: wdspec disabled: - if (os == "linux") and (bits == 64) and debug: https://bugzilla.mozilla.org/show_bug.cgi?id=1313282 - expected: - if not debug: TIMEOUT + if (os == "linux") and (bits == 64) and debug: https://bugzilla.mozilla.org/show_bug.cgi?id=1318724 [contexts.py::test_resize] expected: FAIL - [contexts.py::test_resize_by_script] expected: FAIL diff --git a/testing/web-platform/meta/webdriver/navigation.py.ini b/testing/web-platform/meta/webdriver/navigation.py.ini index 454eb34e083d..02d7c5b25f75 100644 --- a/testing/web-platform/meta/webdriver/navigation.py.ini +++ b/testing/web-platform/meta/webdriver/navigation.py.ini @@ -15,6 +15,5 @@ expected: FAIL [navigation.py::test_get_current_url_matches_location] - expected: - if debug: FAIL + expected: FAIL diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-MANUAL.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-MANUAL.html deleted file mode 100644 index 346ed5662960..000000000000 --- a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-MANUAL.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - Body Element - - - - - - - -

Validation of li characteristic requiring manual testing

- -

The spec states: "If the parent element is an ol element, then the li element has an ordinal value."

-

This manual test is needed to verify that NON-ol element parents do NOT result in an ordinal value.

-

It needs to be manual because the ordinal value assigned to each list element by the user agent is NOT available programmatically. Values which are set either via markup or IDL are available programmatically, but not the calculated values for all the other list items.

-

And, as we cannot be sure how a mistakenly assigned value would be rendered, this test cannot be a reftest.

-

So, please use the buttons to answer the following questions:

- - - - - - - - - - - - - - - - - - - - - - -
HTML MarkupDo you see any sort of sequencing information?
- -
  • Menu Item
  • -
  • Menu Item
  • -
    -
    - - -
    - -
  • - -
  • Toolbar Menu Item
  • -
  • Toolbar Menu Item
  • -
    -
  • -
  • - -
  • Toolbar Menu Item
  • -
  • Toolbar Menu Item
  • -
    -
  • -
    -
    - - -
    -
      -
    • list item
    • -
    • list item
    • -
    • list item
    • -
    -
    - - -
    - -
    - - - - diff --git a/testing/web-platform/tests/tools/webdriver/webdriver/client.py b/testing/web-platform/tests/tools/webdriver/webdriver/client.py index b7d37b925455..8e1813f25f59 100644 --- a/testing/web-platform/tests/tools/webdriver/webdriver/client.py +++ b/testing/web-platform/tests/tools/webdriver/webdriver/client.py @@ -70,6 +70,147 @@ def implicit_wait(self, value): self._implicit_wait = value +class ActionSequence(object): + """API for creating and performing action sequences. + + Each action method adds one or more actions to a queue. When perform() + is called, the queued actions fire in order. + + May be chained together as in:: + + ActionSequence(session, "key", id) \ + .key_down("a") \ + .key_up("a") \ + .perform() + """ + def __init__(self, session, action_type, input_id, pointer_params=None): + """Represents a sequence of actions of one type for one input source. + + :param session: WebDriver session. + :param action_type: Action type; may be "none", "key", or "pointer". + :param input_id: ID of input source. + :param pointer_params: Optional dictionary of pointer parameters. + """ + self.session = session + self._id = input_id + self._type = action_type + self._actions = [] + self._pointer_params = pointer_params + + @property + def dict(self): + d = { + "type": self._type, + "id": self._id, + "actions": self._actions, + } + if self._pointer_params is not None: + d["parameters"] = self._pointer_params + return d + + @command + def perform(self): + """Perform all queued actions.""" + self.session.actions.perform([self.dict]) + + def _key_action(self, subtype, value): + self._actions.append({"type": subtype, "value": value}) + + def _pointer_action(self, subtype, button): + self._actions.append({"type": subtype, "button": button}) + + def pointer_move(self, x, y, duration=None, origin=None): + """Queue a pointerMove action. + + :param x: Destination x-axis coordinate of pointer in CSS pixels. + :param y: Destination y-axis coordinate of pointer in CSS pixels. + :param duration: Number of milliseconds over which to distribute the + move. If None, remote end defaults to 0. + :param origin: Origin of coordinates, either "viewport", "pointer" or + an Element. If None, remote end defaults to "viewport". + """ + # TODO change to pointerMove once geckodriver > 0.14 is available on mozilla-central + action = { + "type": "move", + "x": x, + "y": y + } + if duration is not None: + action["duration"] = duration + if origin is not None: + action["origin"] = origin if isinstance(origin, basestring) else origin.json() + self._actions.append(action) + return self + + def pointer_up(self, button): + """Queue a pointerUp action for `button`. + + :param button: Pointer button to perform action with. + """ + self._pointer_action("pointerUp", button) + return self + + def pointer_down(self, button): + """Queue a pointerDown action for `button`. + + :param button: Pointer button to perform action with. + """ + self._pointer_action("pointerDown", button) + return self + + def key_up(self, value): + """Queue a keyUp action for `value`. + + :param value: Character to perform key action with. + """ + self._key_action("keyUp", value) + return self + + def key_down(self, value): + """Queue a keyDown action for `value`. + + :param value: Character to perform key action with. + """ + self._key_action("keyDown", value) + return self + + def send_keys(self, keys): + """Queue a keyDown and keyUp action for each character in `keys`. + + :param keys: String of keys to perform key actions with. + """ + for c in keys: + self.key_down(c) + self.key_up(c) + return self + + +class Actions(object): + def __init__(self, session): + self.session = session + + @command + def perform(self, actions=None): + """Performs actions by tick from each action sequence in `actions`. + + :param actions: List of input source action sequences. A single action + sequence may be created with the help of + ``ActionSequence.dict``. + """ + body = {"actions": [] if actions is None else actions} + return self.session.send_command("POST", "actions", body) + + @command + def release(self): + return self.session.send_command("DELETE", "actions") + + def sequence(self, *args, **kwargs): + """Return an empty ActionSequence of the designated type. + + See ActionSequence for parameter list. + """ + return ActionSequence(self.session, *args, **kwargs) + class Window(object): def __init__(self, session): self.session = session @@ -190,6 +331,7 @@ def __init__(self, host, port, url_prefix="/", desired_capabilities=None, self.window = Window(self) self.find = Find(self) self.alert = UserPrompt(self) + self.actions = Actions(self) def __enter__(self): self.start() @@ -428,22 +570,26 @@ def send_keys(self, keys): @property @command def text(self): - return self.session.send_command("GET", self.url("text")) + return self.session.send_command("GET", self.url("text"), key="value") @property @command def name(self): - return self.session.send_command("GET", self.url("name")) + return self.session.send_command("GET", self.url("name"), key="value") @command def style(self, property_name): - return self.session.send_command("GET", self.url("css/%s" % property_name)) + return self.session.send_command("GET", self.url("css/%s" % property_name), key="value") @property @command def rect(self): return self.session.send_command("GET", self.url("rect")) + @command + def property(self, name): + return self.session.send_command("GET", self.url("property/%s" % name), key="value") + @command def attribute(self, name): - return self.session.send_command("GET", self.url("attribute/%s" % name)) + return self.session.send_command("GET", self.url("attribute/%s" % name), key="value") diff --git a/testing/web-platform/tests/webdriver/actions/__init__.py b/testing/web-platform/tests/webdriver/actions/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/testing/web-platform/tests/webdriver/actions/conftest.py b/testing/web-platform/tests/webdriver/actions/conftest.py new file mode 100644 index 000000000000..b51a6d4e0cf6 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/conftest.py @@ -0,0 +1,34 @@ +import pytest + + +@pytest.fixture +def key_chain(session): + return session.actions.sequence("key", "keyboard_id") + + +@pytest.fixture +def mouse_chain(session): + return session.actions.sequence( + "pointer", + "pointer_id", + {"pointerType": "mouse"}) + + +@pytest.fixture(autouse=True) +def release_actions(session, request): + # release all actions after each test + # equivalent to a teardown_function, but with access to session fixture + request.addfinalizer(session.actions.release) + + +@pytest.fixture +def key_reporter(session, test_actions_page, request): + """Represents focused input element from `test_keys_page` fixture.""" + input_el = session.find.css("#keys", all=False) + input_el.click() + return input_el + + +@pytest.fixture +def test_actions_page(session, server): + session.url = server.where_is("webdriver/actions/support/test_actions_wdspec.html") diff --git a/testing/web-platform/tests/webdriver/actions/key.py b/testing/web-platform/tests/webdriver/actions/key.py new file mode 100644 index 000000000000..d4f313c47353 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/key.py @@ -0,0 +1,170 @@ +import pytest + +from support.keys import Keys +from support.refine import get_keys, filter_dict, get_events + + +def test_lone_keyup_sends_no_events(session, key_reporter, key_chain): + key_chain.key_up("a").perform() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + session.actions.release() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + + +# TODO - the harness bails with TIMEOUT before all these subtests complete +# The timeout is per file, so move to separate file with longer timeout? +# Need a way to set timeouts in py files (since can't do html meta) +# @pytest.mark.parametrize("name,expected", ALL_EVENTS.items()) +# def test_webdriver_special_key_sends_keydown(session, +# key_reporter, +# key_chain, +# name, +# expected): +# key_chain.key_down(getattr(Keys, name)).perform() +# # only interested in keydown +# first_event = get_events(session)[0] +# # make a copy so we throw out irrelevant keys and compare to events +# expected = dict(expected) +# del expected["value"] +# # check and remove keys that aren't in expected +# assert first_event["type"] == "keydown" +# assert first_event["repeat"] == False +# first_event = filter_dict(first_event, expected) +# assert first_event == expected +# # check that printable character was recorded in input field +# if len(expected["key"]) == 1: +# assert get_keys(key_reporter) == expected["key"] + + +@pytest.mark.parametrize("value,code", [ + (u"a", "KeyA",), + ("a", "KeyA",), + (u"\"", "Quote"), + (u",", "Comma"), + (u"\u00E0", ""), + (u"\u0416", ""), + (u"@", "Digit2"), + (u"\u2603", ""), + (u"\uF6C2", ""), # PUA +]) +def test_single_printable_key_sends_correct_events(session, + key_reporter, + key_chain, + value, + code): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + expected = [ + {"code": code, "key": value, "type": "keydown"}, + {"code": code, "key": value, "type": "keypress"}, + {"code": code, "key": value, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == value + + +@pytest.mark.parametrize("value", [ + (u"\U0001F604"), + (u"\U0001F60D"), +]) +def test_single_emoji_records_correct_key(session, key_reporter, key_chain, value): + # Not using key_chain.send_keys() because we always want to treat value as + # one character here. `len(value)` varies by platform for non-BMP characters, + # so we don't want to iterate over value. + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + # events sent by major browsers are inconsistent so only check key value + assert get_keys(key_reporter) == value + + +@pytest.mark.parametrize("value,code,key", [ + (u"\uE050", "ShiftRight", "Shift"), + (u"\uE053", "OSRight", "Meta"), + (Keys.CONTROL, "ControlLeft", "Control"), +]) +def test_single_modifier_key_sends_correct_events(session, + key_reporter, + key_chain, + value, + code, + key): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + all_events = get_events(session) + expected = [ + {"code": code, "key": key, "type": "keydown"}, + {"code": code, "key": key, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in all_events] + assert events == expected + assert len(get_keys(key_reporter)) == 0 + + +@pytest.mark.parametrize("value,code,key", [ + (Keys.ESCAPE, "Escape", "Escape"), + (Keys.RIGHT, "ArrowRight", "ArrowRight"), +]) +def test_single_nonprintable_key_sends_events(session, + key_reporter, + key_chain, + value, + code, + key): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + expected = [ + {"code": code, "key": key, "type": "keydown"}, + {"code": code, "key": key, "type": "keypress"}, + {"code": code, "key": key, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + if len(events) == 2: + # most browsers don't send a keypress for non-printable keys + assert events == [expected[0], expected[2]] + else: + assert events == expected + assert len(get_keys(key_reporter)) == 0 + + +def test_sequence_of_keydown_printable_keys_sends_events(session, + key_reporter, + key_chain): + key_chain \ + .key_down("a") \ + .key_down("b") \ + .perform() + expected = [ + {"code": "KeyA", "key": "a", "type": "keydown"}, + {"code": "KeyA", "key": "a", "type": "keypress"}, + {"code": "KeyB", "key": "b", "type": "keydown"}, + {"code": "KeyB", "key": "b", "type": "keypress"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == "ab" + + +def test_sequence_of_keydown_character_keys(session, key_reporter, key_chain): + key_chain.send_keys("ef").perform() + expected = [ + {"code": "KeyE", "key": "e", "type": "keydown"}, + {"code": "KeyE", "key": "e", "type": "keypress"}, + {"code": "KeyE", "key": "e", "type": "keyup"}, + {"code": "KeyF", "key": "f", "type": "keydown"}, + {"code": "KeyF", "key": "f", "type": "keypress"}, + {"code": "KeyF", "key": "f", "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == "ef" diff --git a/testing/web-platform/tests/webdriver/actions/mouse.py b/testing/web-platform/tests/webdriver/actions/mouse.py new file mode 100644 index 000000000000..a7192ef3bc89 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/mouse.py @@ -0,0 +1,31 @@ +from support.refine import get_events, filter_dict + + +def test_click_at_coordinates(session, test_actions_page, mouse_chain): + div_point = { + "x": 82, + "y": 187, + } + button = 0 + mouse_chain \ + .pointer_move(div_point["x"], div_point["y"], duration=1000) \ + .pointer_down(button) \ + .pointer_up(button) \ + .perform() + events = get_events(session) + assert len(events) == 4 + for e in events: + if e["type"] != "mousemove": + assert e["pageX"] == div_point["x"] + assert e["pageY"] == div_point["y"] + assert e["target"] == "outer" + if e["type"] != "mousedown": + assert e["buttons"] == 0 + assert e["button"] == button + expected = [ + {"type": "mousedown", "buttons": 1}, + {"type": "mouseup", "buttons": 0}, + {"type": "click", "buttons": 0}, + ] + filtered_events = [filter_dict(e, expected[0]) for e in events] + assert expected == filtered_events[1:] diff --git a/testing/web-platform/tests/webdriver/actions/sequence.py b/testing/web-platform/tests/webdriver/actions/sequence.py new file mode 100644 index 000000000000..960b800dff84 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/sequence.py @@ -0,0 +1,31 @@ +from support.refine import get_keys, filter_dict, get_events + + +def test_no_actions_send_no_events(session, key_reporter, key_chain): + key_chain.perform() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + + +def test_release_char_sequence_sends_keyup_events_in_reverse(session, + key_reporter, + key_chain): + key_chain \ + .key_down("a") \ + .key_down("b") \ + .perform() + # reset so we only see the release events + session.execute_script("resetEvents();") + session.actions.release() + expected = [ + {"code": "KeyB", "key": "b", "type": "keyup"}, + {"code": "KeyA", "key": "a", "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + + +def test_release_no_actions_sends_no_events(session, key_reporter): + session.actions.release() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 diff --git a/testing/web-platform/tests/webdriver/actions/support/__init__.py b/testing/web-platform/tests/webdriver/actions/support/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/testing/web-platform/tests/webdriver/actions/support/keys.py b/testing/web-platform/tests/webdriver/actions/support/keys.py new file mode 100644 index 000000000000..842c3db730ec --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/keys.py @@ -0,0 +1,812 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +The Keys implementation. +""" + +from inspect import getmembers + + +class Keys(object): + """ + Set of special keys codes. + + See also https://w3c.github.io/webdriver/webdriver-spec.html#h-keyboard-actions + """ + + NULL = u"\ue000" + CANCEL = u"\ue001" # ^break + HELP = u"\ue002" + BACKSPACE = u"\ue003" + TAB = u"\ue004" + CLEAR = u"\ue005" + RETURN = u"\ue006" + ENTER = u"\ue007" + SHIFT = u"\ue008" + CONTROL = u"\ue009" + ALT = u"\ue00a" + PAUSE = u"\ue00b" + ESCAPE = u"\ue00c" + SPACE = u"\ue00d" + PAGE_UP = u"\ue00e" + PAGE_DOWN = u"\ue00f" + END = u"\ue010" + HOME = u"\ue011" + LEFT = u"\ue012" + UP = u"\ue013" + RIGHT = u"\ue014" + DOWN = u"\ue015" + INSERT = u"\ue016" + DELETE = u"\ue017" + SEMICOLON = u"\ue018" + EQUALS = u"\ue019" + + NUMPAD0 = u"\ue01a" # number pad keys + NUMPAD1 = u"\ue01b" + NUMPAD2 = u"\ue01c" + NUMPAD3 = u"\ue01d" + NUMPAD4 = u"\ue01e" + NUMPAD5 = u"\ue01f" + NUMPAD6 = u"\ue020" + NUMPAD7 = u"\ue021" + NUMPAD8 = u"\ue022" + NUMPAD9 = u"\ue023" + MULTIPLY = u"\ue024" + ADD = u"\ue025" + SEPARATOR = u"\ue026" + SUBTRACT = u"\ue027" + DECIMAL = u"\ue028" + DIVIDE = u"\ue029" + + F1 = u"\ue031" # function keys + F2 = u"\ue032" + F3 = u"\ue033" + F4 = u"\ue034" + F5 = u"\ue035" + F6 = u"\ue036" + F7 = u"\ue037" + F8 = u"\ue038" + F9 = u"\ue039" + F10 = u"\ue03a" + F11 = u"\ue03b" + F12 = u"\ue03c" + + META = u"\ue03d" + + # More keys from webdriver spec + ZENKAKUHANKAKU = u"\uE040" + R_SHIFT = u"\uE050" + R_CONTROL = u"\uE051" + R_ALT = u"\uE052" + R_META = u"\uE053" + R_PAGEUP = u"\uE054" + R_PAGEDOWN = u"\uE055" + R_END = u"\uE056" + R_HOME = u"\uE057" + R_ARROWLEFT = u"\uE058" + R_ARROWUP = u"\uE059" + R_ARROWRIGHT = u"\uE05A" + R_ARROWDOWN = u"\uE05B" + R_INSERT = u"\uE05C" + R_DELETE = u"\uE05D" + + +ALL_KEYS = getmembers(Keys, lambda x: type(x) == unicode) + +ALL_EVENTS = { + "ADD": { + "code": "", + "ctrl": False, + "key": "+", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue025", + "which": 0, + }, + "ALT": { + "code": "AltLeft", + "ctrl": False, + "key": "Alt", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue00a", + "which": 0, + }, + "BACKSPACE": { + "code": "Backspace", + "ctrl": False, + "key": "Backspace", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue003", + "which": 0, + }, + "CANCEL": { + "code": "", + "ctrl": False, + "key": "Cancel", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue001", + "which": 0, + }, + "CLEAR": { + "code": "", + "ctrl": False, + "key": "Clear", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue005", + "which": 0, + }, + "CONTROL": { + "code": "ControlLeft", + "ctrl": True, + "key": "Control", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue009", + "which": 0, + }, + "DECIMAL": { + "code": "NumpadDecimal", + "ctrl": False, + "key": ".", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue028", + "which": 0, + }, + "DELETE": { + "code": "Delete", + "ctrl": False, + "key": "Delete", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue017", + "which": 0, + }, + "DIVIDE": { + "code": "NumpadDivide", + "ctrl": False, + "key": "/", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue029", + "which": 0, + }, + "DOWN": { + "code": "ArrowDown", + "ctrl": False, + "key": "ArrowDown", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue015", + "which": 0, + }, + "END": { + "code": "End", + "ctrl": False, + "key": "End", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue010", + "which": 0, + }, + "ENTER": { + "code": "NumpadEnter", + "ctrl": False, + "key": "Enter", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue007", + "which": 0, + }, + "EQUALS": { + "code": "", + "ctrl": False, + "key": "=", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue019", + "which": 0, + }, + "ESCAPE": { + "code": "Escape", + "ctrl": False, + "key": "Escape", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00c", + "which": 0, + }, + "F1": { + "code": "F1", + "ctrl": False, + "key": "F1", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue031", + "which": 0, + }, + "F10": { + "code": "F10", + "ctrl": False, + "key": "F10", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03a", + "which": 0, + }, + "F11": { + "code": "F11", + "ctrl": False, + "key": "F11", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03b", + "which": 0, + }, + "F12": { + "code": "F12", + "ctrl": False, + "key": "F12", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03c", + "which": 0, + }, + "F2": { + "code": "F2", + "ctrl": False, + "key": "F2", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue032", + "which": 0, + }, + "F3": { + "code": "F3", + "ctrl": False, + "key": "F3", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue033", + "which": 0, + }, + "F4": { + "code": "F4", + "ctrl": False, + "key": "F4", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue034", + "which": 0, + }, + "F5": { + "code": "F5", + "ctrl": False, + "key": "F5", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue035", + "which": 0, + }, + "F6": { + "code": "F6", + "ctrl": False, + "key": "F6", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue036", + "which": 0, + }, + "F7": { + "code": "F7", + "ctrl": False, + "key": "F7", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue037", + "which": 0, + }, + "F8": { + "code": "F8", + "ctrl": False, + "key": "F8", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue038", + "which": 0, + }, + "F9": { + "code": "F9", + "ctrl": False, + "key": "F9", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue039", + "which": 0, + }, + "HELP": { + "code": "Help", + "ctrl": False, + "key": "Help", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue002", + "which": 0, + }, + "HOME": { + "code": "Home", + "ctrl": False, + "key": "Home", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue011", + "which": 0, + }, + "INSERT": { + "code": "Insert", + "ctrl": False, + "key": "Insert", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue016", + "which": 0, + }, + "LEFT": { + "code": "ArrowLeft", + "ctrl": False, + "key": "ArrowLeft", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue012", + "which": 0, + }, + "META": { + "code": "OSLeft", + "ctrl": False, + "key": "Meta", + "location": 1, + "meta": True, + "shift": False, + "value": u"\ue03d", + "which": 0, + }, + "MULTIPLY": { + "code": "NumpadMultiply", + "ctrl": False, + "key": "*", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue024", + "which": 0, + }, + "NULL": { + "code": "", + "ctrl": False, + "key": "Unidentified", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue000", + "which": 0, + }, + "NUMPAD0": { + "code": "Numpad0", + "ctrl": False, + "key": "0", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01a", + "which": 0, + }, + "NUMPAD1": { + "code": "Numpad1", + "ctrl": False, + "key": "1", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01b", + "which": 0, + }, + "NUMPAD2": { + "code": "Numpad2", + "ctrl": False, + "key": "2", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01c", + "which": 0, + }, + "NUMPAD3": { + "code": "Numpad3", + "ctrl": False, + "key": "3", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01d", + "which": 0, + }, + "NUMPAD4": { + "code": "PageDown", + "ctrl": False, + "key": "4", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01e", + "which": 0, + }, + "NUMPAD5": { + "code": "PageUp", + "ctrl": False, + "key": "5", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01f", + "which": 0, + }, + "NUMPAD6": { + "code": "Numpad6", + "ctrl": False, + "key": "6", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue020", + "which": 0, + }, + "NUMPAD7": { + "code": "Numpad7", + "ctrl": False, + "key": "7", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue021", + "which": 0, + }, + "NUMPAD8": { + "code": "Numpad8", + "ctrl": False, + "key": "8", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue022", + "which": 0, + }, + "NUMPAD9": { + "code": "Numpad9", + "ctrl": False, + "key": "9", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue023", + "which": 0, + }, + "PAGE_DOWN": { + "code": "", + "ctrl": False, + "key": "PageDown", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00f", + "which": 0, + }, + "PAGE_UP": { + "code": "", + "ctrl": False, + "key": "PageUp", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00e", + "which": 0, + }, + "PAUSE": { + "code": "", + "ctrl": False, + "key": "Pause", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00b", + "which": 0, + }, + "RETURN": { + "code": "Enter", + "ctrl": False, + "key": "Return", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue006", + "which": 0, + }, + "RIGHT": { + "code": "ArrowRight", + "ctrl": False, + "key": "ArrowRight", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue014", + "which": 0, + }, + "R_ALT": { + "code": "AltRight", + "ctrl": False, + "key": "Alt", + "location": 2, + "meta": False, + "shift": False, + "value": u"\ue052", + "which": 0, + }, + "R_ARROWDOWN": { + "code": "Numpad2", + "ctrl": False, + "key": "ArrowDown", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05b", + "which": 0, + }, + "R_ARROWLEFT": { + "code": "Numpad4", + "ctrl": False, + "key": "ArrowLeft", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue058", + "which": 0, + }, + "R_ARROWRIGHT": { + "code": "Numpad6", + "ctrl": False, + "key": "ArrowRight", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05a", + "which": 0, + }, + "R_ARROWUP": { + "code": "Numpad8", + "ctrl": False, + "key": "ArrowUp", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue059", + "which": 0, + }, + "R_CONTROL": { + "code": "ControlRight", + "ctrl": True, + "key": "Control", + "location": 2, + "meta": False, + "shift": False, + "value": u"\ue051", + "which": 0, + }, + "R_DELETE": { + "code": "NumpadDecimal", + "ctrl": False, + "key": "Delete", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05d", + "which": 0, + }, + "R_END": { + "code": "Numpad1", + "ctrl": False, + "key": "End", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue056", + "which": 0, + }, + "R_HOME": { + "code": "Numpad7", + "ctrl": False, + "key": "Home", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue057", + "which": 0, + }, + "R_INSERT": { + "code": "Numpad0", + "ctrl": False, + "key": "Insert", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05c", + "which": 0, + }, + "R_META": { + "code": "OSRight", + "ctrl": False, + "key": "Meta", + "location": 2, + "meta": True, + "shift": False, + "value": u"\ue053", + "which": 0, + }, + "R_PAGEDOWN": { + "code": "Numpad3", + "ctrl": False, + "key": "PageDown", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue055", + "which": 0, + }, + "R_PAGEUP": { + "code": "Numpad9", + "ctrl": False, + "key": "PageUp", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue054", + "which": 0, + }, + "R_SHIFT": { + "code": "ShiftRight", + "ctrl": False, + "key": "Shift", + "location": 2, + "meta": False, + "shift": True, + "value": u"\ue050", + "which": 0, + }, + "SEMICOLON": { + "code": "", + "ctrl": False, + "key": ";", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue018", + "which": 0, + }, + "SEPARATOR": { + "code": "NumpadSubtract", + "ctrl": False, + "key": ",", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue026", + "which": 0, + }, + "SHIFT": { + "code": "ShiftLeft", + "ctrl": False, + "key": "Shift", + "location": 1, + "meta": False, + "shift": True, + "value": u"\ue008", + "which": 0, + }, + "SPACE": { + "code": "Space", + "ctrl": False, + "key": " ", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00d", + "which": 0, + }, + "SUBTRACT": { + "code": "", + "ctrl": False, + "key": "-", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue027", + "which": 0, + }, + "TAB": { + "code": "Tab", + "ctrl": False, + "key": "Tab", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue004", + "which": 0, + }, + "UP": { + "code": "ArrowUp", + "ctrl": False, + "key": "ArrowUp", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue013", + "which": 0, + }, + "ZENKAKUHANKAKU": { + "code": "", + "ctrl": False, + "key": "ZenkakuHankaku", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue040", + "which": 0, + } +} diff --git a/testing/web-platform/tests/webdriver/actions/support/refine.py b/testing/web-platform/tests/webdriver/actions/support/refine.py new file mode 100644 index 000000000000..3a6d63e04ce9 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/refine.py @@ -0,0 +1,33 @@ +def get_events(session): + """Return list of key events recorded in the test_keys_page fixture.""" + events = session.execute_script("return allEvents.events;") or [] + # `key` values in `allEvents` may be escaped (see `escapeSurrogateHalf` in + # test_keys_wdspec.html), so this converts them back into unicode literals. + for e in events: + # example: turn "U+d83d" (6 chars) into u"\ud83d" (1 char) + if "key" in e and e["key"].startswith(u"U+"): + key = e["key"] + hex_suffix = key[key.index("+") + 1:] + e["key"] = unichr(int(hex_suffix, 16)) + return events + + +def get_keys(input_el): + """Get printable characters entered into `input_el`. + + :param input_el: HTML input element. + """ + rv = input_el.property("value") + if rv is None: + return "" + else: + return rv + + +def filter_dict(source, d): + """Filter `source` dict to only contain same keys as `d` dict. + + :param source: dictionary to filter. + :param d: dictionary whose keys determine the filtering. + """ + return {k: source[k] for k in d.keys()} diff --git a/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html b/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html new file mode 100644 index 000000000000..38b3885f6f41 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html @@ -0,0 +1,131 @@ + + + + Test Actions + + + + +
    +
    +

    KeyReporter

    + +
    +
    +

    ClickReporter

    +
    +
    +
    +
    +

    Events

    +
    +
    + + diff --git a/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js b/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js deleted file mode 100644 index 482fcf4853f2..000000000000 --- a/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js +++ /dev/null @@ -1,310 +0,0 @@ -/* 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/. */ - -/** - * Test autoFill for different default behaviors. - */ - -add_task(function* test_default_behavior_host() { - let uri1 = NetUtil.newURI("http://typed/"); - let uri2 = NetUtil.newURI("http://visited/"); - let uri3 = NetUtil.newURI("http://bookmarked/"); - let uri4 = NetUtil.newURI("http://tpbk/"); - let uri5 = NetUtil.newURI("http://tagged/"); - - yield PlacesTestUtils.addVisits([ - { uri: uri1, title: "typed", transition: TRANSITION_TYPED }, - { uri: uri2, title: "visited" }, - { uri: uri4, title: "tpbk", transition: TRANSITION_TYPED }, - ]); - yield addBookmark( { uri: uri3, title: "bookmarked" } ); - yield addBookmark( { uri: uri4, title: "tpbk" } ); - yield addBookmark( { uri: uri5, title: "title", tags: ["foo"] } ); - - yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/information-16.png"); - yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png"); - - // RESTRICT TO HISTORY. - Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); - Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false); - Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); - - do_print("Restrict history, common visit, should not autoFill"); - yield check_autocomplete({ - search: "vi", - matches: [ { uri: uri2, title: "visited" } ], - autofilled: "vi", - completed: "vi" - }); - - do_print("Restrict history, typed visit, should autoFill"); - yield check_autocomplete({ - search: "ty", - matches: [ { uri: uri1, title: "typed", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/information-16.png" } ], - autofilled: "typed/", - completed: "typed/" - }); - - // Don't autoFill this one cause it's not typed. - do_print("Restrict history, bookmark, should not autoFill"); - yield check_autocomplete({ - search: "bo", - matches: [ ], - autofilled: "bo", - completed: "bo" - }); - - // Note we don't show this one cause it's not typed. - do_print("Restrict history, typed bookmark, should autoFill"); - yield check_autocomplete({ - search: "tp", - matches: [ { uri: uri4, title: "tpbk", style: [ "autofill", "heuristic" ] } ], - autofilled: "tpbk/", - completed: "tpbk/" - }); - - Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - - // We are not restricting on typed, so we autoFill the bookmark even if we - // are restricted to history. We accept that cause not doing that - // would be a perf hit and the privacy implications are very weak. - do_print("Restrict history, bookmark, autoFill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/error-16.png" } ], - autofilled: "bookmarked/", - completed: "bookmarked/" - }); - - do_print("Restrict history, common visit, autoFill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "vi", - matches: [ { uri: uri2, title: "visited", style: [ "autofill", "heuristic" ] } ], - autofilled: "visited/", - completed: "visited/" - }); - - // RESTRICT TO TYPED. - // This should basically ignore autoFill.typed and acts as if it would be set. - Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true); - - // Typed behavior basically acts like history, but filters on typed. - do_print("Restrict typed, common visit, autoFill.typed = false, should not autoFill"); - yield check_autocomplete({ - search: "vi", - matches: [ ], - autofilled: "vi", - completed: "vi" - }); - - do_print("Restrict typed, typed visit, autofill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "ty", - matches: [ { uri: uri1, title: "typed", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/information-16.png"} ], - autofilled: "typed/", - completed: "typed/" - }); - - do_print("Restrict typed, bookmark, autofill.typed = false, should not autoFill"); - yield check_autocomplete({ - search: "bo", - matches: [ ], - autofilled: "bo", - completed: "bo" - }); - - do_print("Restrict typed, typed bookmark, autofill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "tp", - matches: [ { uri: uri4, title: "tpbk", style: [ "autofill", "heuristic" ] } ], - autofilled: "tpbk/", - completed: "tpbk/" - }); - - // RESTRICT BOOKMARKS. - Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); - Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); - Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - - do_print("Restrict bookmarks, common visit, should not autoFill"); - yield check_autocomplete({ - search: "vi", - matches: [ ], - autofilled: "vi", - completed: "vi" - }); - - do_print("Restrict bookmarks, typed visit, should not autoFill"); - yield check_autocomplete({ - search: "ty", - matches: [ ], - autofilled: "ty", - completed: "ty" - }); - - // Don't autoFill this one cause it's not typed. - do_print("Restrict bookmarks, bookmark, should not autoFill"); - yield check_autocomplete({ - search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], - icon: "chrome://global/skin/icons/error-16.png"} ], - autofilled: "bo", - completed: "bo" - }); - - // Note we don't show this one cause it's not typed. - do_print("Restrict bookmarks, typed bookmark, should autoFill"); - yield check_autocomplete({ - search: "tp", - matches: [ { uri: uri4, title: "tpbk", style: [ "autofill", "heuristic" ] } ], - autofilled: "tpbk/", - completed: "tpbk/" - }); - - Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - - do_print("Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/error-16.png" } ], - autofilled: "bookmarked/", - completed: "bookmarked/" - }); - - // Don't autofill because it's a title. - do_print("Restrict bookmarks, title, autofill.typed = false, should not autoFill"); - yield check_autocomplete({ - search: "# ta", - matches: [ ], - autofilled: "# ta", - completed: "# ta" - }); - - // Don't autofill because it's a tag. - do_print("Restrict bookmarks, tag, autofill.typed = false, should not autoFill"); - yield check_autocomplete({ - search: "+ ta", - matches: [ { uri: uri5, title: "title", tags: [ "foo" ], style: [ "tag" ] } ], - autofilled: "+ ta", - completed: "+ ta" - }); - - yield cleanup(); -}); - -add_task(function* test_default_behavior_url() { - let uri1 = NetUtil.newURI("http://typed/ty/"); - let uri2 = NetUtil.newURI("http://visited/vi/"); - let uri3 = NetUtil.newURI("http://bookmarked/bo/"); - let uri4 = NetUtil.newURI("http://tpbk/tp/"); - - yield PlacesTestUtils.addVisits([ - { uri: uri1, title: "typed", transition: TRANSITION_TYPED }, - { uri: uri2, title: "visited" }, - { uri: uri4, title: "tpbk", transition: TRANSITION_TYPED }, - ]); - yield addBookmark( { uri: uri3, title: "bookmarked" } ); - yield addBookmark( { uri: uri4, title: "tpbk" } ); - - yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/information-16.png"); - yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png"); - - // RESTRICT TO HISTORY. - Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); - Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false); - Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); - Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false); - - do_print("URL: Restrict history, common visit, should not autoFill"); - yield check_autocomplete({ - search: "visited/v", - matches: [ { uri: uri2, title: "visited" } ], - autofilled: "visited/v", - completed: "visited/v" - }); - - do_print("URL: Restrict history, typed visit, should autoFill"); - yield check_autocomplete({ - search: "typed/t", - matches: [ { uri: uri1, title: "typed/ty/", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/information-16.png"} ], - autofilled: "typed/ty/", - completed: "http://typed/ty/" - }); - - // Don't autoFill this one cause it's not typed. - do_print("URL: Restrict history, bookmark, should not autoFill"); - yield check_autocomplete({ - search: "bookmarked/b", - matches: [ ], - autofilled: "bookmarked/b", - completed: "bookmarked/b" - }); - - // Note we don't show this one cause it's not typed. - do_print("URL: Restrict history, typed bookmark, should autoFill"); - yield check_autocomplete({ - search: "tpbk/t", - matches: [ { uri: uri4, title: "tpbk/tp/", style: [ "autofill", "heuristic" ] } ], - autofilled: "tpbk/tp/", - completed: "http://tpbk/tp/" - }); - - // RESTRICT BOOKMARKS. - Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); - Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); - - do_print("URL: Restrict bookmarks, common visit, should not autoFill"); - yield check_autocomplete({ - search: "visited/v", - matches: [ ], - autofilled: "visited/v", - completed: "visited/v" - }); - - do_print("URL: Restrict bookmarks, typed visit, should not autoFill"); - yield check_autocomplete({ - search: "typed/t", - matches: [ ], - autofilled: "typed/t", - completed: "typed/t" - }); - - // Don't autoFill this one cause it's not typed. - do_print("URL: Restrict bookmarks, bookmark, should not autoFill"); - yield check_autocomplete({ - search: "bookmarked/b", - matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], - icon: "chrome://global/skin/icons/error-16.png" } ], - autofilled: "bookmarked/b", - completed: "bookmarked/b" - }); - - // Note we don't show this one cause it's not typed. - do_print("URL: Restrict bookmarks, typed bookmark, should autoFill"); - yield check_autocomplete({ - search: "tpbk/t", - matches: [ { uri: uri4, title: "tpbk/tp/", style: [ "autofill", "heuristic" ] } ], - autofilled: "tpbk/tp/", - completed: "http://tpbk/tp/" - }); - - Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - - do_print("URL: Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); - yield check_autocomplete({ - search: "bookmarked/b", - matches: [ { uri: uri3, title: "bookmarked/bo/", style: [ "autofill", "heuristic" ], - icon: "chrome://global/skin/icons/error-16.png" } ], - autofilled: "bookmarked/bo/", - completed: "http://bookmarked/bo/" - }); - - yield cleanup(); -}); diff --git a/toolkit/modules/sessionstore/Utils.jsm b/toolkit/modules/sessionstore/Utils.jsm index ea374137652d..863bca6f5133 100644 --- a/toolkit/modules/sessionstore/Utils.jsm +++ b/toolkit/modules/sessionstore/Utils.jsm @@ -17,6 +17,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "serializationHelper", "@mozilla.org/network/serialization-helper;1", "nsISerializationHelper"); +function debug(msg) { + Services.console.logStringMessage("Utils: " + msg); +} + this.Utils = Object.freeze({ makeURI: function (url) { return Services.io.newURI(url, null, null); @@ -91,9 +95,13 @@ this.Utils = Object.freeze({ if (!principal_b64) return null; - let principal = serializationHelper.deserializeObject(principal_b64); - principal.QueryInterface(Ci.nsIPrincipal); - - return principal; + try { + let principal = serializationHelper.deserializeObject(principal_b64); + principal.QueryInterface(Ci.nsIPrincipal); + return principal; + } catch (e) { + debug(`Failed to deserialize principal_b64 '${principal_b64}' ${e}`); + } + return null; } }); diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index acdc7ee82a19..085217d4c27f 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -3815,11 +3815,27 @@ XREMain::XRE_mainStartup(bool* aExitFlag) return 1; } + if (!username) { + struct passwd *pw = getpwuid(geteuid()); + if (pw && pw->pw_name) { + // Beware that another call to getpwent/getpwname/getpwuid will overwrite + // pw, but we don't have such another call between here and when username + // is used last. + username = pw->pw_name; + } + } + nsCOMPtr mutexDir; rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(mutexDir)); if (NS_SUCCEEDED(rv)) { - nsAutoCString mutexPath = - program + NS_LITERAL_CSTRING("_") + nsDependentCString(username); + nsAutoCString mutexPath = program + NS_LITERAL_CSTRING("_"); + // In the unlikely even that LOGNAME is not set and getpwuid failed, just + // don't put the username in the mutex directory. It will conflict with + // other users mutex, but the worst that can happen is that they wait for + // MOZ_XREMOTE_START_TIMEOUT_SEC during startup in that case. + if (username) { + mutexPath.Append(username); + } if (profile) { mutexPath.Append(NS_LITERAL_CSTRING("_") + nsDependentCString(profile)); }