Merge m-c to b2ginbound a=merge

This commit is contained in:
Wes Kocher 2015-04-22 17:26:08 -07:00
Родитель 1e10131572 a79764cf6b
Коммит 1974bec813
1141 изменённых файлов: 84442 добавлений и 8034 удалений

Просмотреть файл

@ -17,8 +17,6 @@ class AccessibleWrap;
} // namespace a11y
} // namespace mozilla
struct MaiUtilClass;
extern "C" {
void actionInterfaceInitCB(AtkActionIface* aIface);
void componentInterfaceInitCB(AtkComponentIface* aIface);

Просмотреть файл

@ -23,7 +23,6 @@ struct nsRect;
class nsIFrame;
class nsIAtom;
class nsIPersistentProperties;
class nsView;
namespace mozilla {
namespace a11y {
@ -39,7 +38,6 @@ class HTMLLIAccessible;
class HyperTextAccessible;
class ImageAccessible;
class KeyBinding;
class MathMLAccessible;
class ProxyAccessible;
class Relation;
class RootAccessible;

Просмотреть файл

@ -23,8 +23,6 @@
class nsAccessiblePivot;
class nsIScrollableView;
const uint32_t kDefaultCacheLength = 128;
namespace mozilla {

Просмотреть файл

@ -8,8 +8,6 @@
#include "BaseAccessibles.h"
class nsGenericHTMLElement;
namespace mozilla {
namespace a11y {

Просмотреть файл

@ -8,8 +8,6 @@
#include "HTMLFormControlAccessible.h"
class nsIMutableArray;
namespace mozilla {
namespace a11y {

Просмотреть файл

@ -10,7 +10,6 @@
#include "TableAccessible.h"
#include "TableCellAccessible.h"
class nsITableLayout;
class nsITableCellLayout;
namespace mozilla {

Просмотреть файл

@ -20,11 +20,7 @@ class Accessible;
} // namespace a11y
} // namespace mozilla
class nsINode;
class nsIContent;
class nsIFrame;
class nsIPresShell;
class nsPluginFrame;
// 0e7e6879-854b-4260-bc6e-525b5fb5cf34
#define NS_IACCESSIBILITYSERVICE_IID \

Просмотреть файл

@ -191,6 +191,12 @@ this.EventManager.prototype = {
let acc = aEvent.accessible;
if (acc === this.contentControl.vc.position) {
this.present(Presentation.nameChanged(acc));
} else {
let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
['text', 'all']);
if (liveRegion) {
this.present(Presentation.nameChanged(acc, isPolite));
}
}
break;
}
@ -293,6 +299,12 @@ this.EventManager.prototype = {
if (position === target ||
Utils.getEmbeddedControl(position) === target) {
this.present(Presentation.valueChanged(target));
} else {
let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
['text', 'all']);
if (liveRegion) {
this.present(Presentation.valueChanged(target, isPolite));
}
}
}
}

Просмотреть файл

@ -523,18 +523,19 @@ B2GPresenter.prototype.pivotChanged =
};
B2GPresenter.prototype.nameChanged =
function B2GPresenter_nameChanged(aAccessible) {
function B2GPresenter_nameChanged(aAccessible, aIsPolite = true) {
return {
type: this.type,
details: {
eventType: 'name-change',
data: aAccessible.name
data: aAccessible.name,
options: {enqueue: aIsPolite}
}
};
};
B2GPresenter.prototype.valueChanged =
function B2GPresenter_valueChanged(aAccessible) {
function B2GPresenter_valueChanged(aAccessible, aIsPolite = true) {
// the editable value changes are handled in the text changed presenter
if (Utils.getState(aAccessible).contains(States.EDITABLE)) {
@ -545,7 +546,8 @@ B2GPresenter.prototype.valueChanged =
type: this.type,
details: {
eventType: 'value-change',
data: aAccessible.value
data: aAccessible.value,
options: {enqueue: aIsPolite}
}
};
};

Просмотреть файл

@ -15,8 +15,6 @@
namespace mozilla {
namespace a11y {
struct objc_class;
class RootAccessibleWrap : public RootAccessible
{
public:

Просмотреть файл

@ -44,6 +44,17 @@
document.getElementById('fruit').setAttribute('aria-label', 'banana');
}
function renameSlider() {
document.getElementById('slider').setAttribute(
'aria-label', 'mover');
}
function changeSliderValue() {
document.getElementById('slider').setAttribute('aria-valuenow', '5');
document.getElementById('slider').setAttribute(
'aria-valuetext', 'medium');
}
</script>
<style>
#windows {
@ -89,5 +100,9 @@
</div>
<button id="home">Home</button>
<button id="fruit" aria-label="apple"></button>
<div id="live" aria-live="polite" aria-label="live">
<div id="slider" role="slider" aria-label="slider" aria-valuemin="0"
aria-valuemax="10" aria-valuenow="0"></div>
</div>
</body>
</html>

Просмотреть файл

@ -629,7 +629,7 @@ ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype);
function ExpectedValueChange(aValue, aOptions) {
ExpectedPresent.call(this, {
eventType: 'value-change',
data: [aValue]
data: aValue
}, null, aOptions);
}

Просмотреть файл

@ -58,8 +58,12 @@
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
[ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
[ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['slider', '0', {'string': 'slider'}])],
// Simple traversal backward
[ContentMessages.simpleMovePrevious,
new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
[ContentMessages.simpleMovePrevious,
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
[ContentMessages.simpleMovePrevious,
@ -92,7 +96,7 @@
// Move from an inner frame to the last element in the parent doc
[ContentMessages.simpleMoveLast,
new ExpectedCursorChange(
['apple', {'string': 'pushbutton'}], { b2g_todo: true })],
['slider', '0', {'string': 'slider'}], { b2g_todo: true })],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
@ -147,6 +151,12 @@
new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
[doc.defaultView.renameFruit, new ExpectedNameChange('banana')],
// Name and value changes inside a live-region (no cursor present)
[doc.defaultView.renameSlider,
new ExpectedNameChange('mover')],
[doc.defaultView.changeSliderValue,
new ExpectedValueChange('medium')],
// Blur button and reset cursor
[ContentMessages.focusSelector('button#fruit', true), null],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
@ -222,14 +232,14 @@
// Open dialog in outer doc, while cursor is also in outer doc
[ContentMessages.simpleMoveLast,
new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])],
new ExpectedCursorChange(['mover'])],
[doc.defaultView.showAlert,
new ExpectedCursorChange(['This is an alert!',
{'string': 'headingLevel', 'args': [1]},
{'string': 'dialog'}])],
[doc.defaultView.hideAlert,
new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])],
new ExpectedCursorChange(['mover'])],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'],

Просмотреть файл

@ -14,7 +14,6 @@
#include "nsCOMPtr.h"
#include "nsRefPtrHashtable.h"
class nsIArray;
class nsIContent;
namespace mozilla {

Просмотреть файл

@ -14,8 +14,6 @@
#include "XULMenuAccessible.h"
#include "XULSelectControlAccessible.h"
class nsIWeakReference;
namespace mozilla {
namespace a11y {

Просмотреть файл

@ -62,7 +62,7 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
'browser.selfsupport.url': 'http://localhost/repair-dummy',
'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',

Просмотреть файл

@ -19,7 +19,7 @@
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
"browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
"browser.selfsupport.url": "http://localhost/repair-dummy",
"browser.selfsupport.url": "https://localhost/selfsupport-dummy",
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
"browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
"browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",

Просмотреть файл

@ -323,7 +323,7 @@ pref("media.fragmented-mp4.gonk.enabled", true);
pref("media.video-queue.default-size", 3);
// optimize images' memory usage
pref("image.decode-only-on-draw.enabled", true);
pref("image.decode-only-on-draw.enabled", false);
pref("image.mem.allow_locking_in_content_processes", true);
// Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
// Almost everything that was factored into 'max_decoded_image_kb' is now stored
@ -1124,8 +1124,5 @@ pref("dom.mozSettings.allowForceReadOnly", false);
// RequestSync API is enabled by default on B2G.
pref("dom.requestSync.enabled", true);
// Use vsync aligned rendering
pref("gfx.vsync.hw-vsync.enabled", true);
pref("gfx.vsync.compositor", true);
// Resample touch events on b2g
pref("gfx.touch.resample", true);
pref("gfx.vsync.refreshdriver", true);

Просмотреть файл

@ -30,14 +30,19 @@ html xul|scrollbar {
pointer-events: none;
}
/* Scrollbar code will reset the margin to the correct side depending on
layout.scrollbar.side pref */
xul|scrollbar[orient="vertical"] {
margin-left: -8px;
-moz-margin-start: -8px;
min-width: 8px;
max-width: 8px;
}
/* workaround for bug 1119057: as -moz-margin-start may not work as expected,
* force a right margin value in RTL mode. */
[dir="rtl"] xul|scrollbar[root="true"][orient="vertical"] {
-moz-margin-start: unset;
margin-right: -8px;
}
xul|scrollbar[orient="vertical"] xul|thumb {
max-width: 6px !important;
min-width: 6px !important;

Просмотреть файл

@ -209,6 +209,7 @@
@RESPATH@/components/dom_json.xpt
@RESPATH@/components/dom_messages.xpt
@RESPATH@/components/dom_power.xpt
@RESPATH@/components/dom_push.xpt
@RESPATH@/components/dom_quota.xpt
@RESPATH@/components/dom_range.xpt
@RESPATH@/components/dom_security.xpt
@ -631,7 +632,7 @@
@RESPATH@/components/AppsService.manifest
@RESPATH@/components/Push.js
@RESPATH@/components/Push.manifest
@RESPATH@/components/PushServiceLauncher.js
@RESPATH@/components/PushNotificationService.js
@RESPATH@/components/InterAppComm.manifest
@RESPATH@/components/InterAppCommService.js

Просмотреть файл

@ -30,6 +30,7 @@ externalProtocolUnknown=<Unknown>
externalProtocolChkMsg=Remember my choice for all links of this type.
externalProtocolLaunchBtn=Launch application
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.

Просмотреть файл

@ -374,6 +374,7 @@
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
<h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
@ -401,6 +402,7 @@
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
<div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>

Просмотреть файл

@ -79,6 +79,9 @@
case "phishingBlocked" :
initPage_phishing();
break;
case "unwantedBlocked" :
initPage_unwanted();
break;
}
}
@ -87,7 +90,7 @@
*/
function initPage_malware()
{
// Remove phishing strings
// Remove phishing and unwanted strings
var el = document.getElementById("errorTitleText_phishing");
el.parentNode.removeChild(el);
@ -97,18 +100,57 @@
el = document.getElementById("errorLongDescText_phishing");
el.parentNode.removeChild(el);
el = document.getElementById("errorTitleText_unwanted");
el.parentNode.removeChild(el);
el = document.getElementById("errorShortDescText_unwanted");
el.parentNode.removeChild(el);
el = document.getElementById("errorLongDescText_unwanted");
el.parentNode.removeChild(el);
// Set sitename
document.getElementById("malware_sitename").textContent = getHostString();
document.title = document.getElementById("errorTitleText_malware")
.innerHTML;
}
/**
* Initialize custom strings and functionality for blocked malware case
*/
function initPage_unwanted()
{
// Remove phishing and malware strings
var el = document.getElementById("errorTitleText_phishing");
el.parentNode.removeChild(el);
el = document.getElementById("errorShortDescText_phishing");
el.parentNode.removeChild(el);
el = document.getElementById("errorLongDescText_phishing");
el.parentNode.removeChild(el);
el = document.getElementById("errorTitleText_malware");
el.parentNode.removeChild(el);
el = document.getElementById("errorShortDescText_malware");
el.parentNode.removeChild(el);
el = document.getElementById("errorLongDescText_malware");
el.parentNode.removeChild(el);
// Set sitename
document.getElementById("unwanted_sitename").textContent = getHostString();
document.title = document.getElementById("errorTitleText_unwanted")
.innerHTML;
}
/**
* Initialize custom strings and functionality for blocked phishing case
*/
function initPage_phishing()
{
// Remove malware strings
// Remove malware and unwanted strings
var el = document.getElementById("errorTitleText_malware");
el.parentNode.removeChild(el);
@ -118,6 +160,15 @@
el = document.getElementById("errorLongDescText_malware");
el.parentNode.removeChild(el);
el = document.getElementById("errorTitleText_unwanted");
el.parentNode.removeChild(el);
el = document.getElementById("errorShortDescText_unwanted");
el.parentNode.removeChild(el);
el = document.getElementById("errorLongDescText_unwanted");
el.parentNode.removeChild(el);
// Set sitename
document.getElementById("phishing_sitename").textContent = getHostString();
document.title = document.getElementById("errorTitleText_phishing")
@ -161,6 +212,7 @@
<div id="errorTitle">
<h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
<h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
<h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
</div>
<div id="errorLongContent">
@ -169,12 +221,14 @@
<div id="errorShortDesc">
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
</div>
<!-- Long Description -->
<div id="errorLongDesc">
<p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
<p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
<p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
</div>
<!-- Action buttons -->

Просмотреть файл

@ -2659,7 +2659,7 @@ let BrowserOnClick = {
msg.data.sslStatusAsString);
break;
case "Browser:SiteBlockedError":
this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
this.onAboutBlocked(msg.data.elementId, msg.data.reason,
msg.data.isTopFrame, msg.data.location);
break;
case "Browser:EnableOnlineMode":
@ -2843,10 +2843,15 @@ let BrowserOnClick = {
}
},
onAboutBlocked: function (elementId, isMalware, isTopFrame, location) {
// Depending on what page we are displaying here (malware/phishing)
onAboutBlocked: function (elementId, reason, isTopFrame, location) {
// Depending on what page we are displaying here (malware/phishing/unwanted)
// use the right strings and links for each.
let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
let bucketName = "WARNING_PHISHING_PAGE_";
if (reason === 'malware') {
bucketName = "WARNING_MALWARE_PAGE_";
} else if (reason === 'unwanted') {
bucketName = "WARNING_UNWANTED_PAGE_";
}
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
let nsISecTel = Ci.nsISecurityUITelemetry;
bucketName += isTopFrame ? "TOP_" : "FRAME_";
@ -2857,33 +2862,19 @@ let BrowserOnClick = {
break;
case "reportButton":
// This is the "Why is this site blocked" button. For malware,
// we can fetch a site-specific report, for phishing, we redirect
// to the generic page describing phishing protection.
// This is the "Why is this site blocked" button. We redirect
// to the generic page describing phishing/malware protection.
// We log even if malware/phishing info URL couldn't be found:
// We log even if malware/phishing/unwanted info URL couldn't be found:
// the measurement is for how many users clicked the WHY BLOCKED button
secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
if (isMalware) {
// Get the stop badware "why is this blocked" report url,
// append the current url, and go there.
try {
let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
reportURL += location;
gBrowser.loadURI(reportURL);
} catch (e) {
Components.utils.reportError("Couldn't get malware report URL: " + e);
}
}
else { // It's a phishing site, not malware
openHelpLink("phishing-malware", false, "current");
}
openHelpLink("phishing-malware", false, "current");
break;
case "ignoreWarningButton":
secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
this.ignoreWarningButton(isMalware);
this.ignoreWarningButton(reason);
break;
}
},
@ -2910,7 +2901,7 @@ let BrowserOnClick = {
}
},
ignoreWarningButton: function (isMalware) {
ignoreWarningButton: function (reason) {
// Allow users to override and continue through to the site,
// but add a notify bar as a reminder, so that they don't lose
// track after, e.g., tab switching.
@ -2929,7 +2920,7 @@ let BrowserOnClick = {
}];
let title;
if (isMalware) {
if (reason === 'malware') {
title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
buttons[1] = {
label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
@ -2938,7 +2929,7 @@ let BrowserOnClick = {
openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
}
};
} else {
} else if (reason === 'phishing') {
title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
buttons[1] = {
label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
@ -2947,6 +2938,10 @@ let BrowserOnClick = {
openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
}
};
} else if (reason === 'unwanted') {
title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
// There is no button for reporting errors since Google doesn't currently
// provide a URL endpoint for these reports.
}
let notificationBox = gBrowser.getNotificationBox();
@ -3994,6 +3989,11 @@ var XULBrowserWindow = {
},
showTooltip: function (x, y, tooltip) {
if (Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService).
getCurrentSession()) {
return;
}
// The x,y coordinates are relative to the <browser> element using
// the chrome zoom level.
let elt = document.getElementById("remoteBrowserTooltip");

Просмотреть файл

@ -123,6 +123,13 @@ let handleContentContextMenu = function (event) {
InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
}
// Set the event target first as the copy image command needs it to
// determine what was context-clicked on. Then, update the state of the
// commands on the context menu.
docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
.setCommandNode(event.target);
event.target.ownerDocument.defaultView.updateCommands("contentcontextmenu");
let customMenuItems = PageMenuChild.build(event.target);
let principal = doc.nodePrincipal;
sendSyncMessage("contextmenu",
@ -377,9 +384,15 @@ let ClickEventHandler = {
},
onAboutBlocked: function (targetElement, ownerDoc) {
var reason = 'phishing';
if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
reason = 'malware';
} else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
reason = 'unwanted';
}
sendAsyncMessage("Browser:SiteBlockedError", {
location: ownerDoc.location.href,
isMalware: /e=malwareBlocked/.test(ownerDoc.documentURI),
reason: reason,
elementId: targetElement.getAttribute("id"),
isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
});

Просмотреть файл

@ -628,7 +628,9 @@ nsContextMenu.prototype = {
this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
// First, do checks for nodes that never have children.
if (this.target.nodeType == Node.ELEMENT_NODE) {
// See if the user clicked on an image.
// See if the user clicked on an image. This check mirrors
// nsDocumentViewer::GetInImage. Make sure to update both if this is
// changed.
if (this.target instanceof Ci.nsIImageLoadingContent &&
this.target.currentURI) {
this.onImage = true;

Просмотреть файл

@ -486,6 +486,11 @@ Sanitizer.prototype = {
.getService(Ci.nsISiteSecurityService);
sss.clearAll();
// Clear all push notification subscriptions
var push = Cc["@mozilla.org/push/NotificationService;1"]
.getService(Ci.nsIPushNotificationService);
push.clearAll();
TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS");
},

Просмотреть файл

@ -144,7 +144,6 @@ skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
[browser_bookmark_titles.js]
skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
[browser_bug304198.js]
skip-if = e10s
[browser_bug321000.js]
skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
[browser_bug329212.js]
@ -215,7 +214,6 @@ skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
[browser_bug562649.js]
[browser_bug563588.js]
[browser_bug565575.js]
skip-if = e10s
[browser_bug565667.js]
skip-if = toolkit != "cocoa"
[browser_bug567306.js]
@ -250,7 +248,6 @@ skip-if = e10s && debug
[browser_bug647886.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1093373 - Relies on browser.sessionHistory
[browser_bug655584.js]
skip-if = e10s
[browser_bug664672.js]
[browser_bug676619.js]
skip-if = buildapp == 'mulet' || os == "mac" # mac: Intermittent failures, bug 925225
@ -337,7 +334,6 @@ skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
[browser_menuButtonFitts.js]
skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
[browser_middleMouse_noJSPaste.js]
skip-if = e10s # Bug 921952 - Content:Click event issues
[browser_minimize.js]
skip-if = e10s # Bug 1100664 - test directly access content docShells (TypeError: gBrowser.docShell is null)
[browser_mixedcontent_securityflags.js]
@ -388,7 +384,7 @@ skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtil
[browser_save_link_when_window_navigates.js]
skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_video.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
skip-if = buildapp == 'mulet'
[browser_save_video_frame.js]
[browser_scope.js]
[browser_searchSuggestionUI.js]
@ -465,7 +461,6 @@ skip-if = (os == "win" && !debug) || e10s # Bug 1007418
[browser_windowopen_reflows.js]
skip-if = buildapp == 'mulet'
[browser_wyciwyg_urlbarCopying.js]
skip-if = e10s # Bug 1100703 - test directly manipulates content (content.document.getElementById)
[browser_zbug569342.js]
skip-if = e10s # Bug 1094240 - has findbar-related failures
[browser_registerProtocolHandler_notification.js]

Просмотреть файл

@ -2,9 +2,7 @@
* 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/. */
function test() {
waitForExplicitFinish();
add_task(function* () {
let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
charsToDelete = 5;
@ -13,112 +11,96 @@ function test() {
partialURLTab = gBrowser.addTab();
testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
deletedURLTab.linkedBrowser.loadURI(testURL);
fullURLTab.linkedBrowser.loadURI(testURL);
partialURLTab.linkedBrowser.loadURI(testURL);
yield Promise.all([loaded1, loaded2, loaded3]);
testURL = gURLBar.trimValue(testURL);
testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
function cleanUp() {
gBrowser.removeTab(fullURLTab);
gBrowser.removeTab(partialURLTab);
gBrowser.removeTab(deletedURLTab);
}
function cycleTabs() {
gBrowser.selectedTab = fullURLTab;
function* cycleTabs() {
yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
gBrowser.selectedTab = partialURLTab;
yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
is(gURLBar.textValue, testPartialURL, 'gURLBar.textValue should be testPartialURL after switching back to partialURLTab');
gBrowser.selectedTab = deletedURLTab;
yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
is(gURLBar.textValue, '', 'gURLBar.textValue should be "" after switching back to deletedURLTab');
gBrowser.selectedTab = fullURLTab;
yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
}
// function borrowed from browser_bug386835.js
function load(tab, url, cb) {
tab.linkedBrowser.addEventListener("load", function (event) {
event.currentTarget.removeEventListener("load", arguments.callee, true);
cb();
}, true);
tab.linkedBrowser.loadURI(url);
}
function urlbarBackspace(cb) {
gBrowser.selectedBrowser.focus();
gURLBar.addEventListener("focus", function () {
gURLBar.removeEventListener("focus", arguments.callee, false);
function urlbarBackspace() {
return new Promise((resolve, reject) => {
gBrowser.selectedBrowser.focus();
gURLBar.addEventListener("input", function () {
gURLBar.removeEventListener("input", arguments.callee, false);
cb();
resolve();
}, false);
executeSoon(function () {
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
});
}, false);
gURLBar.focus();
gURLBar.focus();
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
});
}
function prepareDeletedURLTab(cb) {
gBrowser.selectedTab = deletedURLTab;
function* prepareDeletedURLTab() {
yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to deletedURLTab');
// simulate the user removing the whole url from the location bar
gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true);
urlbarBackspace(function () {
is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
cb();
});
yield urlbarBackspace();
is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
}
}
function prepareFullURLTab(cb) {
gBrowser.selectedTab = fullURLTab;
function* prepareFullURLTab() {
yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to fullURLTab');
cb();
}
function preparePartialURLTab(cb) {
gBrowser.selectedTab = partialURLTab;
function* preparePartialURLTab() {
yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to partialURLTab');
// simulate the user removing part of the url from the location bar
gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false);
var deleted = 0;
urlbarBackspace(function () {
let deleted = 0;
while (deleted < charsToDelete) {
yield urlbarBackspace(arguments.callee);
deleted++;
if (deleted < charsToDelete) {
urlbarBackspace(arguments.callee);
} else {
is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
cb();
}
});
}
is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
}
}
function runTests() {
testURL = gURLBar.trimValue(testURL);
testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
// prepare the three tabs required by this test
// prepare the three tabs required by this test
prepareFullURLTab(function () {
preparePartialURLTab(function () {
prepareDeletedURLTab(function () {
// now cycle the tabs and make sure everything looks good
cycleTabs();
cleanUp();
finish();
});
});
});
}
// First tab
yield* prepareFullURLTab();
yield* preparePartialURLTab();
yield* prepareDeletedURLTab();
// now cycle the tabs and make sure everything looks good
yield* cycleTabs();
cleanUp();
});
load(deletedURLTab, testURL, function() {
load(fullURLTab, testURL, function() {
load(partialURLTab, testURL, runTests);
});
});
}

Просмотреть файл

@ -1,13 +1,14 @@
function test() {
add_task(function* () {
gBrowser.selectedBrowser.focus();
BrowserOpenTab();
yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => BrowserOpenTab(), false);
ok(gURLBar.focused, "location bar is focused for a new tab");
gBrowser.selectedTab = gBrowser.tabs[0];
yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]);
ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
gBrowser.selectedTab = gBrowser.tabs[1];
yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
gBrowser.removeCurrentTab();
}
});

Просмотреть файл

@ -4,20 +4,20 @@
// Bug 655584 - awesomebar suggestions don't update after tab is closed
function test() {
add_task(function* () {
var tab1 = gBrowser.addTab();
var tab2 = gBrowser.addTab();
// When urlbar in a new tab is focused, and a tab switch occurs,
// the urlbar popup should be closed
gBrowser.selectedTab = tab2;
yield BrowserTestUtils.switchTab(gBrowser, tab2);
gURLBar.focus(); // focus the urlbar in the tab we will switch to
gBrowser.selectedTab = tab1;
yield BrowserTestUtils.switchTab(gBrowser, tab1);
gURLBar.openPopup();
gBrowser.selectedTab = tab2;
yield BrowserTestUtils.switchTab(gBrowser, tab2);
ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
// cleanup
gBrowser.removeCurrentTab();
gBrowser.removeCurrentTab();
}
});

Просмотреть файл

@ -1,7 +1,9 @@
// This test is used to check copy and paste in editable areas to ensure that non-text
// types (html and images) are copied to and pasted from the clipboard properly.
let testPage = "<div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>";
let testPage = "<body style='margin: 0'><img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" +
" <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" +
"</body>";
add_task(function*() {
let tab = gBrowser.addTab();
@ -12,7 +14,12 @@ add_task(function*() {
yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
let results = yield ContentTask.spawn(browser, {}, function* () {
const modifier = (content.navigator.platform.indexOf("Mac") >= 0) ?
Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
let results = yield ContentTask.spawn(browser, { modifier: modifier },
function* (arg) {
var doc = content.document;
var main = doc.getElementById("main");
main.focus();
@ -20,10 +27,7 @@ add_task(function*() {
const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
const modifier = (content.navigator.platform.indexOf("Mac") >= 0) ?
Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
const modifier = arg.modifier;
function sendKey(key)
{
if (utils.sendKeyEvent("keydown", key, 0, modifier)) {
@ -46,7 +50,7 @@ add_task(function*() {
selection.modify("extend", "right", "word");
selection.modify("extend", "right", "word");
yield new content.Promise((resolve, reject) => {
yield new Promise((resolve, reject) => {
addEventListener("copy", function copyEvent(event) {
removeEventListener("copy", copyEvent, true);
// The data is empty as the selection is copied during the event default phase.
@ -59,7 +63,7 @@ add_task(function*() {
selection.modify("move", "right", "line");
yield new content.Promise((resolve, reject) => {
yield new Promise((resolve, reject) => {
addEventListener("paste", function copyEvent(event) {
removeEventListener("paste", copyEvent, true);
let clipboardData = event.clipboardData;
@ -80,7 +84,7 @@ add_task(function*() {
selection.modify("extend", "left", "word");
selection.modify("extend", "left", "character");
yield new content.Promise((resolve, reject) => {
yield new Promise((resolve, reject) => {
addEventListener("cut", function copyEvent(event) {
removeEventListener("cut", copyEvent, true);
event.clipboardData.setData("text/plain", "Some text");
@ -94,7 +98,7 @@ add_task(function*() {
selection.modify("move", "left", "line");
yield new content.Promise((resolve, reject) => {
yield new Promise((resolve, reject) => {
addEventListener("paste", function copyEvent(event) {
removeEventListener("paste", copyEvent, true);
let clipboardData = event.clipboardData;
@ -110,6 +114,7 @@ add_task(function*() {
});
is(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>", "Copy and paste html 2");
return results;
});
@ -118,6 +123,61 @@ add_task(function*() {
ok(results[t].startsWith("PASSED"), results[t]);
}
// Next, check that the Copy Image command works.
// The context menu needs to be opened to properly initialize for the copy
// image command to run.
let contextMenu = document.getElementById("contentAreaContextMenu");
let contextMenuShown = promisePopupShown(contextMenu);
BrowserTestUtils.synthesizeMouseAtCenter("#img", { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
yield contextMenuShown;
document.getElementById("context-copyimage-contents").doCommand();
contextMenu.hidePopup();
yield promisePopupHidden(contextMenu);
// Focus the content again
yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
let expectedContent = yield ContentTask.spawn(browser, { modifier: modifier },
function* (arg) {
var doc = content.document;
var main = doc.getElementById("main");
main.focus();
yield new Promise((resolve, reject) => {
addEventListener("paste", function copyEvent(event) {
removeEventListener("paste", copyEvent, true);
let clipboardData = event.clipboardData;
// DataTransfer doesn't support the image types yet, so only text/html
// will be present.
if (clipboardData.getData("text/html") !=
'<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">') {
reject();
}
resolve();
}, true)
const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
const modifier = arg.modifier;
if (utils.sendKeyEvent("keydown", "v", 0, modifier)) {
utils.sendKeyEvent("keypress", "v", "v".charCodeAt(0), modifier);
}
utils.sendKeyEvent("keyup", "v", 0, modifier);
});
// Return the new content which should now include an image.
return main.innerHTML;
});
is(expectedContent, '<i>Italic</i> <img id="img" tabindex="1" ' +
'src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
'Test <b>Bold</b> After<b></b>', "Paste after copy image");
gBrowser.removeCurrentTab();
});

Просмотреть файл

@ -3,51 +3,32 @@
const middleMousePastePref = "middlemouse.contentLoadURL";
const autoScrollPref = "general.autoScroll";
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref(middleMousePastePref, true);
Services.prefs.setBoolPref(autoScrollPref, false);
let tab = gBrowser.selectedTab = gBrowser.addTab();
add_task(function* () {
yield pushPrefs([middleMousePastePref, true], [autoScrollPref, false]);
registerCleanupFunction(function () {
Services.prefs.clearUserPref(middleMousePastePref);
Services.prefs.clearUserPref(autoScrollPref);
gBrowser.removeTab(tab);
});
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
addPageShowListener(function () {
let pagePrincipal = gBrowser.contentPrincipal;
// copy javascript URI to the clipboard
let url = "javascript:http://www.example.com/";
waitForClipboard(url,
function() {
Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper)
.copyString(url, document);
},
function () {
// Middle click on the content area
info("Middle clicking");
EventUtils.sendMouseEvent({type: "click", button: 1}, gBrowser);
},
function() {
ok(false, "Failed to copy URL to the clipboard");
finish();
}
);
addPageShowListener(function () {
is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
finish();
let url = "javascript:http://www.example.com/";
yield new Promise((resolve, reject) => {
SimpleTest.waitForClipboard(url, () => {
Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper)
.copyString(url, document);
}, resolve, () => {
ok(false, "Clipboard copy failed");
reject();
});
});
}
function addPageShowListener(func) {
gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
func();
});
}
let middlePagePromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
// Middle click on the content area
info("Middle clicking");
yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {button: 1}, gBrowser.selectedBrowser);
yield middlePagePromise;
is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
gBrowser.removeTab(tab);
});

Просмотреть файл

@ -8,70 +8,68 @@ MockFilePicker.init(window);
* TestCase for bug 564387
* <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
*/
function test() {
waitForExplicitFinish();
add_task(function* () {
var fileName;
let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html");
yield loadPromise;
gBrowser.addEventListener("pageshow", function pageShown(event) {
if (event.target.location == "about:blank")
return;
gBrowser.removeEventListener("pageshow", pageShown);
let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
executeSoon(function () {
document.addEventListener("popupshown", contextMenuOpened);
yield BrowserTestUtils.synthesizeMouseAtCenter("#video1",
{ type: "contextmenu", button: 2 },
gBrowser.selectedBrowser);
info("context menu click on video1");
var video1 = gBrowser.contentDocument.getElementById("video1");
EventUtils.synthesizeMouseAtCenter(video1,
{ type: "contextmenu", button: 2 },
gBrowser.contentWindow);
info("context menu click on video1");
});
});
yield popupShownPromise;
function contextMenuOpened(event) {
event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
info("context menu opened on video1");
info("context menu opened on video1");
// Create the folder the video will be saved into.
var destDir = createTemporarySaveDirectory();
var destFile = destDir.clone();
// Create the folder the video will be saved into.
var destDir = createTemporarySaveDirectory();
var destFile = destDir.clone();
MockFilePicker.displayDirectory = destDir;
MockFilePicker.showCallback = function(fp) {
fileName = fp.defaultString;
destFile.append (fileName);
MockFilePicker.returnFiles = [destFile];
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
};
MockFilePicker.displayDirectory = destDir;
MockFilePicker.showCallback = function(fp) {
fileName = fp.defaultString;
destFile.append(fileName);
MockFilePicker.returnFiles = [destFile];
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
};
let transferCompletePromise = new Promise((resolve) => {
function onTransferComplete(downloadSuccess) {
ok(downloadSuccess, "Video file should have been downloaded successfully");
is(fileName, "web-video1-expectedName.ogv",
"Video file name is correctly retrieved from Content-Disposition http header");
resolve();
}
mockTransferCallback = onTransferComplete;
mockTransferRegisterer.register();
});
registerCleanupFunction(function () {
mockTransferRegisterer.unregister();
MockFilePicker.cleanup();
destDir.remove(true);
});
registerCleanupFunction(function () {
mockTransferRegisterer.unregister();
MockFilePicker.cleanup();
destDir.remove(true);
});
// Select "Save Video As" option from context menu
var saveVideoCommand = document.getElementById("context-savevideo");
saveVideoCommand.doCommand();
info("context-savevideo command executed");
// Select "Save Video As" option from context menu
var saveVideoCommand = document.getElementById("context-savevideo");
saveVideoCommand.doCommand();
info("context-savevideo command executed");
event.target.hidePopup();
}
let contextMenu = document.getElementById("contentAreaContextMenu");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
contextMenu.hidePopup();
yield popupHiddenPromise;
function onTransferComplete(downloadSuccess) {
ok(downloadSuccess, "Video file should have been downloaded successfully");
yield transferCompletePromise;
});
is(fileName, "web-video1-expectedName.ogv",
"Video file name is correctly retrieved from Content-Disposition http header");
finish();
}
}
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)

Просмотреть файл

@ -1,39 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
let url = "http://mochi.test:8888/browser/browser/base/content/test/general/test_wyciwyg_copying.html";
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
tab.linkedBrowser.addEventListener("pageshow", function () {
let btn = content.document.getElementById("btn");
executeSoon(function () {
EventUtils.synthesizeMouseAtCenter(btn, {}, content);
let currentURL = gBrowser.currentURI.spec;
ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
executeSoon(function () {
testURLBarCopy(url, endTest);
});
});
}, false);
function endTest() {
while (gBrowser.tabs.length > 1)
gBrowser.removeCurrentTab();
finish();
}
function testURLBarCopy(targetValue, cb) {
function testURLBarCopy(targetValue) {
return new Promise((resolve, reject) => {
info("Expecting copy of: " + targetValue);
waitForClipboard(targetValue, function () {
gURLBar.focus();
gURLBar.select();
goDoCommand("cmd_copy");
}, cb, cb);
}
}, resolve, () => {
ok(false, "Clipboard copy failed");
reject();
});
});
}
add_task(function* () {
const url = "http://mochi.test:8888/browser/browser/base/content/test/general/test_wyciwyg_copying.html";
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser);
let currentURL = gBrowser.currentURI.spec;
ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
yield testURLBarCopy(url);
while (gBrowser.tabs.length > 1)
gBrowser.removeCurrentTab();
});

Просмотреть файл

@ -468,6 +468,17 @@ loop.shared.actions = (function() {
LeaveRoom: Action.define("leaveRoom", {
}),
/**
* Used to record a link click for metrics purposes.
*/
RecordClick: Action.define("recordClick", {
// Note: for ToS and Privacy links, this should be the link, for
// other links this should be a generic description so that we don't
// record what users are clicking, just the information about the fact
// they clicked the link in that spot (e.g. "Shared URL").
linkInfo: String
}),
/**
* Requires detailed information on sad feedback.
*/

Просмотреть файл

@ -29,6 +29,7 @@
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-36116321-15', 'auto');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
}
</script>
@ -129,6 +130,7 @@
<script type="text/javascript" src="js/standaloneMozLoop.js"></script>
<script type="text/javascript" src="js/fxOSMarketplace.js"></script>
<script type="text/javascript" src="js/standaloneRoomViews.js"></script>
<script type="text/javascript" src="js/standaloneMetricsStore.js"></script>
<script type="text/javascript" src="js/webapp.js"></script>
<script>

Просмотреть файл

@ -0,0 +1,210 @@
/* 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/. */
var loop = loop || {};
loop.store = loop.store || {};
/**
* The standalone metrics store is used to log activities to
* analytics.
*
* Where possible we log events via receiving actions. However, some
* combinations of actions and events require us to listen directly to
* changes in the activeRoomStore so that we gain the benefit of the logic
* in that store.
*/
loop.store.StandaloneMetricsStore = (function() {
"use strict";
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
loop.store.metrics = loop.store.metrics || {};
var METRICS_GA_CATEGORY = loop.store.METRICS_GA_CATEGORY = {
general: "/conversation/ interactions",
download: "Firefox Downloads"
};
var METRICS_GA_ACTIONS = loop.store.METRICS_GA_ACTIONS = {
audioMute: "audio mute",
button: "button click",
download: "download button click",
faceMute: "face mute",
link: "link click",
pageLoad: "page load messages",
success: "success",
support: "support link click"
}
var StandaloneMetricsStore = loop.store.createStore({
actions: [
"gotMediaPermission",
"joinRoom",
"leaveRoom",
"mediaConnected",
"recordClick"
],
/**
* Initializes the store and starts listening to the activeRoomStore.
*
* @param {Object} options Options for the store, should include a
* reference to the activeRoomStore.
*/
initialize: function(options) {
options = options || {};
if (!options.activeRoomStore) {
throw new Error("Missing option activeRoomStore");
}
// Don't bother listening if we're not storing metrics.
// I'd love for ga to be an option, but that messes up the function
// working properly.
if (window.ga) {
this.activeRoomStore = options.activeRoomStore;
this.listenTo(options.activeRoomStore, "change",
this._onActiveRoomStoreChange.bind(this));
}
},
/**
* Returns initial state data for this store. These are mainly reflections
* of activeRoomStore so we can match initial states for change tracking
* purposes.
*
* @return {Object} The initial store state.
*/
getInitialStoreState: function() {
return {
audioMuted: false,
roomState: ROOM_STATES.INIT,
videoMuted: false
};
},
/**
* Saves an event to ga.
*
* @param {String} category The category for the event.
* @param {String} action The type of action.
* @param {String} label The label detailing the action.
*/
_storeEvent: function(category, action, label) {
// ga might not be defined if donottrack is enabled, see index.html.
if (!window.ga) {
return;
}
// For now all we need to do is forward onto ga.
window.ga("send", "event", category, action, label);
},
/**
* Handles media permssion being obtained.
*/
gotMediaPermission: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
"Media granted");
},
/**
* Handles the user clicking the join room button.
*/
joinRoom: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
},
/**
* Handles the user clicking the leave room button.
*/
leaveRoom: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Leave conversation");
},
/**
* Handles notification that two-way media has been achieved.
*/
mediaConnected: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
"Media connected")
},
/**
* Handles recording link clicks.
*
* @param {sharedActions.RecordClick} actionData The data associated with
* the link.
*/
recordClick: function(actionData) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick,
actionData.linkInfo);
},
/**
* Handles notifications that the activeRoomStore has changed, updating
* the metrics for room state and mute state as necessary.
*/
_onActiveRoomStoreChange: function() {
var roomStore = this.activeRoomStore.getStoreState();
this._checkRoomState(roomStore.roomState, roomStore.failureReason);
this._checkMuteState("audio", roomStore.audioMuted);
this._checkMuteState("video", roomStore.videoMuted);
},
/**
* Handles checking of the room state to look for events we need to send.
*
* @param {String} roomState The new room state.
* @param {String} failureReason Optional, if the room is in the failure
* state, this should contain the reason for
* the failure.
*/
_checkRoomState: function(roomState, failureReason) {
if (this._storeState.roomState === roomState) {
return;
}
this._storeState.roomState = roomState;
if (roomState === ROOM_STATES.FAILED &&
failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
"Link expired or invalid");
}
if (roomState === ROOM_STATES.FULL) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
"Room full");
}
},
/**
* Handles check of the mute state to look for events we need to send.
*
* @param {String} type The type of mute being adjusted, i.e. "audio" or
* "video".
* @param {Boolean} muted The new state of mute
*/
_checkMuteState: function(type, muted) {
var muteItem = type + "Muted";
if (this._storeState[muteItem] === muted) {
return;
}
this._storeState[muteItem] = muted;
var muteType = type === "audio" ? METRICS_GA_ACTIONS.audioMute : METRICS_GA_ACTIONS.faceMute;
var muteState = muted ? "mute" : "unmute";
this._storeEvent(METRICS_GA_CATEGORY.general, muteType, muteState);
}
});
return StandaloneMetricsStore;
})();

Просмотреть файл

@ -160,11 +160,23 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
var StandaloneRoomHeader = React.createClass({displayName: "StandaloneRoomHeader",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
React.createElement("header", null,
React.createElement("h1", null, mozL10n.get("clientShortname2")),
React.createElement("a", {target: "_blank", href: loop.config.generalSupportUrl},
React.createElement("a", {href: loop.config.generalSupportUrl,
onClick: this.recordClick,
target: "_blank"},
React.createElement("i", {className: "icon icon-help"})
)
)
@ -173,7 +185,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
var StandaloneRoomFooter = React.createClass({displayName: "StandaloneRoomFooter",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
@ -189,10 +208,21 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}))
}
},
render: function() {
return (
React.createElement("footer", null,
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()}}),
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()},
onClick: this.recordClick}),
React.createElement("div", {className: "footer-logo"})
)
);
@ -201,10 +231,17 @@ loop.standaloneRoomViews = (function(mozL10n) {
var StandaloneRoomContextItem = React.createClass({displayName: "StandaloneRoomContextItem",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
receivingScreenShare: React.PropTypes.bool,
roomContextUrl: React.PropTypes.object
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Shared URL"
}));
},
render: function() {
if (!this.props.roomContextUrl ||
!this.props.roomContextUrl.location) {
@ -225,7 +262,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("img", {src: this.props.roomContextUrl.thumbnail}),
React.createElement("div", {className: "standalone-context-url-description-wrapper"},
this.props.roomContextUrl.description,
React.createElement("br", null), React.createElement("a", {href: location}, location)
React.createElement("br", null), React.createElement("a", {href: location,
onClick: this.recordClick,
target: "_blank"}, location)
)
)
);
@ -234,6 +273,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
var StandaloneRoomContextView = React.createClass({displayName: "StandaloneRoomContextView",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
receivingScreenShare: React.PropTypes.bool.isRequired,
roomContextUrls: React.PropTypes.array,
roomName: React.PropTypes.string,
@ -259,6 +299,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("div", {className: "standalone-room-info"},
React.createElement("h2", {className: "room-name"}, this.props.roomName),
React.createElement(StandaloneRoomContextItem, {
dispatcher: this.props.dispatcher,
receivingScreenShare: this.props.receivingScreenShare,
roomContextUrl: roomContextUrl})
)
@ -517,7 +558,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.createElement("div", {className: "room-conversation-wrapper"},
React.createElement("div", {className: "beta-logo"}),
React.createElement(StandaloneRoomHeader, null),
React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}),
React.createElement(StandaloneRoomInfoArea, {roomState: this.state.roomState,
failureReason: this.state.failureReason,
joinRoom: this.joinRoom,
@ -527,6 +568,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("div", {className: "video-layout-wrapper"},
React.createElement("div", {className: "conversation room-conversation"},
React.createElement(StandaloneRoomContextView, {
dispatcher: this.props.dispatcher,
receivingScreenShare: this.state.receivingScreenShare,
roomContextUrls: this.state.roomContextUrls,
roomName: this.state.roomName,
@ -556,7 +598,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement(loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView, {
marketplaceSrc: this.state.marketplaceSrc,
onMarketplaceMessage: this.state.onMarketplaceMessage}),
React.createElement(StandaloneRoomFooter, null)
React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
)
);
}
@ -564,6 +606,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
return {
StandaloneRoomContextView: StandaloneRoomContextView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomView: StandaloneRoomView
};
})(navigator.mozL10n);

Просмотреть файл

@ -160,11 +160,23 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
var StandaloneRoomHeader = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
<header>
<h1>{mozL10n.get("clientShortname2")}</h1>
<a target="_blank" href={loop.config.generalSupportUrl}>
<a href={loop.config.generalSupportUrl}
onClick={this.recordClick}
target="_blank">
<i className="icon icon-help"></i>
</a>
</header>
@ -173,7 +185,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
var StandaloneRoomFooter = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
@ -189,10 +208,21 @@ loop.standaloneRoomViews = (function(mozL10n) {
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}))
}
},
render: function() {
return (
<footer>
<p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
<p dangerouslySetInnerHTML={{__html: this._getContent()}}
onClick={this.recordClick}></p>
<div className="footer-logo" />
</footer>
);
@ -201,10 +231,17 @@ loop.standaloneRoomViews = (function(mozL10n) {
var StandaloneRoomContextItem = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
receivingScreenShare: React.PropTypes.bool,
roomContextUrl: React.PropTypes.object
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Shared URL"
}));
},
render: function() {
if (!this.props.roomContextUrl ||
!this.props.roomContextUrl.location) {
@ -225,7 +262,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
<img src={this.props.roomContextUrl.thumbnail} />
<div className="standalone-context-url-description-wrapper">
{this.props.roomContextUrl.description}
<br /><a href={location}>{location}</a>
<br /><a href={location}
onClick={this.recordClick}
target="_blank">{location}</a>
</div>
</div>
);
@ -234,6 +273,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
var StandaloneRoomContextView = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
receivingScreenShare: React.PropTypes.bool.isRequired,
roomContextUrls: React.PropTypes.array,
roomName: React.PropTypes.string,
@ -259,6 +299,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
<div className="standalone-room-info">
<h2 className="room-name">{this.props.roomName}</h2>
<StandaloneRoomContextItem
dispatcher={this.props.dispatcher}
receivingScreenShare={this.props.receivingScreenShare}
roomContextUrl={roomContextUrl} />
</div>
@ -517,7 +558,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
<div className="beta-logo" />
<StandaloneRoomHeader />
<StandaloneRoomHeader dispatcher={this.props.dispatcher} />
<StandaloneRoomInfoArea roomState={this.state.roomState}
failureReason={this.state.failureReason}
joinRoom={this.joinRoom}
@ -527,6 +568,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
<div className="video-layout-wrapper">
<div className="conversation room-conversation">
<StandaloneRoomContextView
dispatcher={this.props.dispatcher}
receivingScreenShare={this.state.receivingScreenShare}
roomContextUrls={this.state.roomContextUrls}
roomName={this.state.roomName}
@ -556,7 +598,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
<loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView
marketplaceSrc={this.state.marketplaceSrc}
onMarketplaceMessage={this.state.onMarketplaceMessage} />
<StandaloneRoomFooter />
<StandaloneRoomFooter dispatcher={this.props.dispatcher} />
</div>
);
}
@ -564,6 +606,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
return {
StandaloneRoomContextView: StandaloneRoomContextView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomView: StandaloneRoomView
};
})(navigator.mozL10n);

Просмотреть файл

@ -1087,8 +1087,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
activeRoomStore: activeRoomStore
});
loop.store.StoreMixin.register({feedbackStore: feedbackStore});
loop.store.StoreMixin.register({
feedbackStore: feedbackStore,
// This isn't used in any views, but is saved here to ensure it
// is kept alive.
standaloneMetricsStore: standaloneMetricsStore
});
window.addEventListener("unload", function() {
dispatcher.dispatch(new sharedActions.WindowUnload());

Просмотреть файл

@ -1087,8 +1087,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
feedbackClient: feedbackClient
});
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
activeRoomStore: activeRoomStore
});
loop.store.StoreMixin.register({feedbackStore: feedbackStore});
loop.store.StoreMixin.register({
feedbackStore: feedbackStore,
// This isn't used in any views, but is saved here to ensure it
// is kept alive.
standaloneMetricsStore: standaloneMetricsStore
});
window.addEventListener("unload", function() {
dispatcher.dispatch(new sharedActions.WindowUnload());

Просмотреть файл

@ -60,12 +60,14 @@
<script src="../../standalone/content/js/standaloneMozLoop.js"></script>
<script src="../../standalone/content/js/fxOSMarketplace.js"></script>
<script src="../../standalone/content/js/standaloneRoomViews.js"></script>
<script src="../../standalone/content/js/standaloneMetricsStore.js"></script>
<script src="../../standalone/content/js/webapp.js"></script>
<!-- Test scripts -->
<script src="standalone_client_test.js"></script>
<script src="standaloneAppStore_test.js"></script>
<script src="standaloneMozLoop_test.js"></script>
<script src="standaloneRoomViews_test.js"></script>
<script src="standaloneMetricsStore_test.js"></script>
<script src="webapp_test.js"></script>
<script src="multiplexGum_test.js"></script>
<script>

Просмотреть файл

@ -0,0 +1,142 @@
/* 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/. */
var expect = chai.expect;
describe("loop.store.StandaloneMetricsStore", function() {
"use strict";
var sandbox, dispatcher, store, fakeActiveRoomStore;
var sharedActions = loop.shared.actions;
var METRICS_GA_CATEGORY = loop.store.METRICS_GA_CATEGORY;
var METRICS_GA_ACTIONS = loop.store.METRICS_GA_ACTIONS;
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
beforeEach(function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
window.ga = sinon.stub();
var fakeStore = loop.store.createStore({
getInitialStoreState: function() {
return {
audioMuted: false,
roomState: ROOM_STATES.INIT,
videoMuted: false
};
}
});
fakeActiveRoomStore = new fakeStore(dispatcher);
store = new loop.store.StandaloneMetricsStore(dispatcher, {
activeRoomStore: fakeActiveRoomStore
});
});
afterEach(function() {
sandbox.restore();
delete window.ga;
});
describe("Action Handlers", function() {
beforeEach(function() {
});
it("should log an event on GotMediaPermission", function() {
store.gotMediaPermission();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
"Media granted");
});
it("should log an event on JoinRoom", function() {
store.joinRoom();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
});
it("should log an event on LeaveRoom", function() {
store.leaveRoom();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Leave conversation");
});
it("should log an event on MediaConnected", function() {
store.mediaConnected();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
"Media connected");
});
it("should log an event on RecordClick", function() {
store.recordClick(new sharedActions.RecordClick({
linkInfo: "fake"
}));
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick,
"fake");
})
});
describe("Store Change Handlers", function() {
it("should log an event on room full", function() {
fakeActiveRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
"Room full");
});
it("should log an event when the room is expired or invalid", function() {
fakeActiveRoomStore.setStoreState({
roomState: ROOM_STATES.FAILED,
failureReason: FAILURE_DETAILS.EXPIRED_OR_INVALID
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
"Link expired or invalid");
});
it("should log an event when video mute is changed", function() {
fakeActiveRoomStore.setStoreState({
videoMuted: true
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.faceMute,
"mute");
});
it("should log an event when audio mute is changed", function() {
fakeActiveRoomStore.setStoreState({
audioMuted: true
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.audioMute,
"mute");
});
});
});

Просмотреть файл

@ -45,7 +45,10 @@ describe("loop.standaloneRoomViews", function() {
});
function mountTestComponent(extraProps) {
var props = _.extend({ receivingScreenShare: false }, extraProps);
var props = _.extend({
dispatcher: dispatcher,
receivingScreenShare: false
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomContextView, props));
@ -98,6 +101,48 @@ describe("loop.standaloneRoomViews", function() {
expect(view.getDOMNode().querySelector(".standalone-context-url")).eql(null);
});
it("should dispatch a RecordClick action when the link is clicked", function() {
var view = mountTestComponent({
roomName: "Mark's room",
roomContextUrls: [{
description: "Mark's super page",
location: "http://invalid.com",
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
}]
});
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".standalone-context-url-description-wrapper > a"));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: "Shared URL"
}));
});
});
describe("StandaloneRoomHeader", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomHeader, {
dispatcher: dispatcher
}));
}
it("should dispatch a RecordClick action when the support link is clicked", function() {
var view = mountTestComponent();
TestUtils.Simulate.click(view.getDOMNode().querySelector("a"));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
});
});
describe("StandaloneRoomView", function() {

Просмотреть файл

@ -34,6 +34,25 @@ function testMalware(event) {
var style = content.getComputedStyle(el, null);
is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for malware");
// Now launch the unwanted software test
window.addEventListener("DOMContentLoaded", testUnwanted, true);
content.location = "http://www.itisatrap.org/firefox/unwanted.html";
}
function testUnwanted(event) {
if (event.target != gBrowser.selectedBrowser.contentDocument) {
return;
}
window.removeEventListener("DOMContentLoaded", testUnwanted, true);
// Confirm that "Ignore this warning" is visible - bug 422410
var el = content.document.getElementById("ignoreWarningButton");
ok(el, "Ignore warning button should be present for unwanted software");
var style = content.getComputedStyle(el, null);
is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for unwanted software");
// Now launch the phishing test
window.addEventListener("DOMContentLoaded", testPhishing, true);
content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";

Просмотреть файл

@ -1,5 +1,5 @@
// Force SafeBrowsing to be initialized for the tests
Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple");
Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
SafeBrowsing.init();

Просмотреть файл

@ -11,9 +11,6 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {controller, inspector} = yield openAnimationInspector();
is(controller.animationPlayers.length, 0,
"There are no AnimationPlayerFront objects at first");
info("Selecting an animated node");
// selectNode waits for the inspector-updated event before resolving, which
// means the controller.PLAYERS_UPDATED_EVENT event has been emitted before

Просмотреть файл

@ -10,15 +10,17 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Apply 2 finite animations to the test node");
info("Select the test node");
yield selectNode(".still", inspector);
info("Apply 2 finite animations to the test node and wait for the widgets to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".still",
attributeName: "class",
attributeValue: "ball still multi-finite"
});
info("Select the test node");
yield selectNode(".still", inspector);
yield onUiUpdated;
is(controller.animationPlayers.length, 2, "2 animation players exist");

Просмотреть файл

@ -14,15 +14,17 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Start an animation on the test node");
info("Select the test node");
yield selectNode(".still", inspector);
info("Start an animation on the test node and wait for the widget to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".still",
attributeName: "class",
attributeValue: "ball still short"
});
info("Select the node");
yield selectNode(".still", inspector);
yield onUiUpdated;
info("Wait until the animation ends");
let widget = panel.playerWidgets[0];

Просмотреть файл

@ -31,7 +31,8 @@ add_task(function*() {
info("Faking an older server version by setting " +
"AnimationsController.hasSetCurrentTime to false");
yield selectNode("body", inspector);
// Selecting <div.still> to make sure no widgets are displayed in the panel.
yield selectNode(".still", inspector);
controller.hasSetCurrentTime = false;
info("Selecting the animated node again");
@ -46,13 +47,12 @@ add_task(function*() {
ok(container.children[0].classList.contains("toggle"),
"The first button is the play/pause button");
yield selectNode("body", inspector);
yield selectNode(".still", inspector);
controller.hasSetCurrentTime = true;
info("Faking an older server version by setting " +
"AnimationsController.hasSetPlaybackRate to false");
yield selectNode("body", inspector);
controller.hasSetPlaybackRate = false;
info("Selecting the animated node again");
@ -65,6 +65,6 @@ add_task(function*() {
ok(!container.querySelector("select"),
"The playback rate select does not exist");
yield selectNode("body", inspector);
yield selectNode(".still", inspector);
controller.hasSetPlaybackRate = true;
});

Просмотреть файл

@ -10,6 +10,9 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the non-animated test node");
yield selectNode(".still", inspector);
ok(!panel.toggleAllButtonEl.classList.contains("paused"),
"The toggle button is in its running state by default");

Просмотреть файл

@ -882,6 +882,14 @@ InplaceEditor.prototype = {
increment *= smallIncrement;
}
// Use default cursor movement rather than providing auto-suggestions.
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
this._preventSuggestions = true;
}
let cycling = false;
if (increment && this._incrementValue(increment) ) {
this._updateSize();

Просмотреть файл

@ -27,6 +27,9 @@ support-files =
[browser_filter-editor-05.js]
[browser_filter-editor-06.js]
[browser_filter-editor-07.js]
[browser_filter-editor-08.js]
[browser_filter-editor-09.js]
[browser_filter-editor-10.js]
[browser_flame-graph-01.js]
[browser_flame-graph-02.js]
[browser_flame-graph-03a.js]

Просмотреть файл

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value using
// arrow keys, applying multiplier using alt/shift on number-type filters
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
const container = doc.querySelector("#container");
const initialValue = "blur(2px)";
let widget = new CSSFilterEditorWidget(container, initialValue);
let value = 2;
triggerKey = triggerKey.bind(widget);
info("Test simple arrow keys");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value using down arrow");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value using down arrow");
info("Test shift key multiplier");
triggerKey(38, "shiftKey");
value += FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should increase value by fast multiplier using up arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value by fast multiplier using down arrow");
info("Test alt key multiplier");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should increase value by slow multiplier using up arrow");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), `${value}px`,
"Should decrease value by slow multiplier using down arrow");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector(".filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function() {}
});
}

Просмотреть файл

@ -0,0 +1,123 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value when cursor is
// on a number using arrow keys, applying multiplier using alt/shift on strings
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
const container = doc.querySelector("#container");
const initialValue = "drop-shadow(rgb(0, 0, 0) 1px 1px 0px)";
let widget = new CSSFilterEditorWidget(container, initialValue);
widget.el.querySelector("input").setSelectionRange(13, 13);
let value = 1;
triggerKey = triggerKey.bind(widget);
info("Test simple arrow keys");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value using down arrow");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value using down arrow");
info("Test shift key multiplier");
triggerKey(38, "shiftKey");
value += FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase value by fast multiplier using up arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value by fast multiplier using down arrow");
info("Test alt key multiplier");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase value by slow multiplier using up arrow");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease value by slow multiplier using down arrow");
triggerKey(40, "shiftKey");
value -= FAST_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease to negative");
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should decrease negative numbers correctly");
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase negative values correctly");
triggerKey(40, "altKey");
triggerKey(40, "altKey");
value -= SLOW_VALUE_MULTIPLIER * 2;
is(widget.getValueAt(0), val(value),
"Should decrease float numbers correctly");
triggerKey(38, "altKey");
value += SLOW_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should increase float numbers correctly");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector(".filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function() {}
});
}
function val(value) {
let v = value.toFixed(1);
if (v.indexOf(".0") > -1) {
v = v.slice(0, -2);
}
return `rgb(0, 0, 0) ${v}px 1px 0px`;
}

Просмотреть файл

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the Filter Editor Widget inputs increase/decrease value when cursor is
// on a number using arrow keys if cursor is behind/mid/after the number strings
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
const container = doc.querySelector("#container");
const initialValue = "drop-shadow(rgb(0, 0, 0) 10px 1px 0px)";
let widget = new CSSFilterEditorWidget(container, initialValue);
const input = widget.el.querySelector("input");
let value = 10;
triggerKey = triggerKey.bind(widget);
info("Test increment/decrement of string-type numbers without selection");
input.setSelectionRange(14, 14);
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor in the middle of number");
input.setSelectionRange(13, 13);
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor before the number");
input.setSelectionRange(15, 15);
triggerKey(40);
value -= DEFAULT_VALUE_MULTIPLIER;
is(widget.getValueAt(0), val(value),
"Should work with cursor after the number");
info("Test increment/decrement of string-type numbers with a selection");
input.setSelectionRange(13, 15);
triggerKey(38);
input.setSelectionRange(13, 18);
triggerKey(38);
value += DEFAULT_VALUE_MULTIPLIER * 2;
is(widget.getValueAt(0), val(value),
"Should work if a there is a selection, starting with the number");
triggerKey = null;
});
// Triggers the specified keyCode and modifier key on
// first filter's input
function triggerKey(key, modifier) {
const filter = this.el.querySelector(".filters").children[0];
const input = filter.querySelector("input");
this._keyDown({
target: input,
keyCode: key,
[modifier]: true,
preventDefault: function() {}
});
}
function val(value) {
let v = value.toFixed(1);
if (v.indexOf(".0") > -1) {
v = v.slice(0, -2);
}
return `rgb(0, 0, 0) ${v}px 1px 0px`;
}

Просмотреть файл

@ -113,6 +113,7 @@ function CSSFilterEditorWidget(el, value = "") {
this._mouseMove = this._mouseMove.bind(this);
this._mouseUp = this._mouseUp.bind(this);
this._mouseDown = this._mouseDown.bind(this);
this._keyDown = this._keyDown.bind(this);
this._input = this._input.bind(this);
this._initMarkup();
@ -195,6 +196,7 @@ CSSFilterEditorWidget.prototype = {
this.addButton.addEventListener("click", this._addButtonClick);
this.list.addEventListener("click", this._removeButtonClick);
this.list.addEventListener("mousedown", this._mouseDown);
this.list.addEventListener("keydown", this._keyDown);
// These events are event delegators for
// drag-drop re-ordering and label-dragging
@ -205,9 +207,77 @@ CSSFilterEditorWidget.prototype = {
this.list.addEventListener("input", this._input);
},
_getFilterElementIndex: function(el) {
return [...this.list.children].indexOf(el);
},
_keyDown: function(e) {
if (e.target.tagName.toLowerCase() !== "input" ||
(e.keyCode !== 40 && e.keyCode !== 38)) {
return;
}
let input = e.target;
const direction = e.keyCode === 40 ? -1 : 1;
let multiplier = DEFAULT_VALUE_MULTIPLIER;
if (e.altKey) {
multiplier = SLOW_VALUE_MULTIPLIER;
} else if (e.shiftKey) {
multiplier = FAST_VALUE_MULTIPLIER;
}
const filterEl = e.target.closest(".filter");
const index = this._getFilterElementIndex(filterEl);
const filter = this.filters[index];
// Filters that have units are number-type filters. For them,
// the value can be incremented/decremented simply.
// For other types of filters (e.g. drop-shadow) we need to check
// if the keypress happened close to a number first.
if (filter.unit) {
let startValue = parseFloat(e.target.value);
let value = startValue + direction * multiplier;
const [min, max] = this._definition(filter.name).range;
value = value < min ? min :
value > max ? max : value;
input.value = fixFloat(value);
this.updateValueAt(index, value);
} else {
let selectionStart = input.selectionStart;
let num = getNeighbourNumber(input.value, selectionStart);
if (!num) {
return;
}
let {start, end, value} = num;
let split = input.value.split("");
let computed = fixFloat(value + direction * multiplier),
dotIndex = computed.indexOf(".0");
if (dotIndex > -1) {
computed = computed.slice(0, -2);
selectionStart = selectionStart > start + dotIndex ?
start + dotIndex :
selectionStart;
}
split.splice(start, end - start, computed);
value = split.join("");
input.value = value;
this.updateValueAt(index, value);
input.setSelectionRange(selectionStart, selectionStart);
}
e.preventDefault();
},
_input: function(e) {
let filterEl = e.target.closest(".filter"),
index = [...this.list.children].indexOf(filterEl),
index = this._getFilterElementIndex(filterEl),
filter = this.filters[index],
def = this._definition(filter.name);
@ -231,7 +301,7 @@ CSSFilterEditorWidget.prototype = {
} else if (e.target.classList.contains("devtools-draglabel")) {
let label = e.target,
input = filterEl.querySelector("input"),
index = [...this.list.children].indexOf(filterEl);
index = this._getFilterElementIndex(filterEl);
this._dragging = {
index, label, input,
@ -273,7 +343,7 @@ CSSFilterEditorWidget.prototype = {
}
let filterEl = e.target.closest(".filter");
let index = [...this.list.children].indexOf(filterEl);
let index = this._getFilterElementIndex(filterEl);
this.removeAt(index);
},
@ -622,6 +692,7 @@ CSSFilterEditorWidget.prototype = {
this.addButton.removeEventListener("click", this._addButtonClick);
this.list.removeEventListener("click", this._removeButtonClick);
this.list.removeEventListener("mousedown", this._mouseDown);
this.list.removeEventListener("keydown", this._keyDown);
// These events are used for drag drop re-ordering
this.win.removeEventListener("mousemove", this._mouseMove);
@ -718,3 +789,40 @@ function tokenizeComputedFilter(css) {
return filters;
}
/**
* Finds neighbour number characters of an index in a string
* the numbers may be floats (containing dots)
* It's assumed that the value given to this function is a valid number
*
* @param {String} string
* The string containing numbers
* @param {Number} index
* The index to look for neighbours for
* @return {Object}
* returns null if no number is found
* value: The number found
* start: The number's starting index
* end: The number's ending index
*/
function getNeighbourNumber(string, index) {
if (!/\d/.test(string)) {
return null;
}
let left = /-?[0-9.]*$/.exec(string.slice(0, index)),
right = /-?[0-9.]*/.exec(string.slice(index));
left = left ? left[0] : "";
right = right ? right[0] : "";
if (!right && !left) {
return null;
}
return {
value: fixFloat(left + right, true),
start: index - left.length,
end: index + right.length
};
}

Просмотреть файл

@ -5,11 +5,9 @@
// A test to check the 'Open Link in new tab' functionality in the
// context menu item for stylesheets (bug 992947).
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
waitForExplicitFinish();
add_task(function*() {
let panel = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
let ui = panel.UI;
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
yield rightClickStyleSheet(ui, ui.editors[0]);
is(ui._openLinkNewTabItem.getAttribute("disabled"), "false", "The menu item is not disabled");
@ -73,7 +71,7 @@ function rightClickStyleSheet(ui, editor) {
EventUtils.synthesizeMouseAtCenter(
editor.summary.querySelector(".stylesheet-name"),
{button: 2, type: "contextmenu"},
gPanelWindow);
ui._window);
return defer.promise;
}
@ -91,7 +89,7 @@ function rightClickInlineStyleSheet(ui, editor) {
EventUtils.synthesizeMouseAtCenter(
editor.summary.querySelector(".stylesheet-name"),
{button: 2, type: "contextmenu"},
gPanelWindow);
ui._window);
return defer.promise;
}
@ -109,7 +107,7 @@ function rightClickNoStyleSheet(ui) {
EventUtils.synthesizeMouseAtCenter(
ui._panelDoc.querySelector("#splitview-tpl-summary-stylesheet"),
{button: 2, type: "contextmenu"},
gPanelWindow);
ui._window);
return defer.promise;
}

Просмотреть файл

@ -12,7 +12,6 @@ let TargetFactory = devtools.TargetFactory;
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let gPanelWindow;
// Import the GCLI test helper
@ -64,7 +63,6 @@ function navigateTo(url) {
function* cleanup()
{
gPanelWindow = null;
while (gBrowser.tabs.length > 1) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
@ -87,56 +85,6 @@ let openStyleEditorForURL = Task.async(function* (url, win) {
return { tab, toolbox, panel, ui };
});
function addTabAndOpenStyleEditors(count, callback, uri) {
let deferred = promise.defer();
let currentCount = 0;
let panel;
addTabAndCheckOnStyleEditorAdded(p => panel = p, function (editor) {
currentCount++;
info(currentCount + " of " + count + " editors opened: "
+ editor.styleSheet.href);
if (currentCount == count) {
if (callback) {
callback(panel);
}
deferred.resolve(panel);
}
});
if (uri) {
content.location = uri;
}
return deferred.promise;
}
function addTabAndCheckOnStyleEditorAdded(callbackOnce, callbackOnAdded) {
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openStyleEditorInWindow(window, function (panel) {
// Execute the individual callback with the panel argument.
callbackOnce(panel);
// Report editors that already opened while loading.
for (let editor of panel.UI.editors) {
callbackOnAdded(editor);
}
// Report new editors added afterwards.
panel.UI.on("editor-added", (event, editor) => callbackOnAdded(editor));
});
}, true);
}
function openStyleEditorInWindow(win, callback) {
let target = TargetFactory.forTab(win.gBrowser.selectedTab);
win.gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
let panel = toolbox.getCurrentPanel();
gPanelWindow = panel._panelWin;
panel.UI._alwaysDisableAnimations = true;
callback(panel);
});
}
/**
* Loads shared/frame-script-utils.js in the specified tab.
*

Просмотреть файл

@ -42,6 +42,10 @@ let testData = [
["VK_BACK_SPACE", "di", -1, 0],
["VK_BACK_SPACE", "d", -1, 0],
["VK_BACK_SPACE", "", -1, 0],
["VK_HOME", "", -1, 0],
["VK_END", "", -1, 0],
["VK_PAGE_UP", "", -1, 0],
["VK_PAGE_DOWN", "", -1, 0],
["f", "fill", 0, MAX_ENTRIES],
["i", "fill", 0, 4],
["VK_LEFT", "fill", -1, 0],
@ -76,8 +80,8 @@ function* testCompletion([key, completion, index, total], editor, view) {
let onSuggest;
if (/(left|right|back_space|escape)/ig.test(key)) {
info("Adding event listener for left|right|back_space|escape keys");
if (/(left|right|back_space|escape|home|end|page_up|page_down)/ig.test(key)) {
info("Adding event listener for left|right|back_space|escape|home|end|page_up|page_down keys");
onSuggest = once(editor.input, "keypress");
} else {
info("Waiting for after-suggest event on the editor");

Просмотреть файл

@ -218,6 +218,7 @@
@RESPATH@/components/dom_offline.xpt
@RESPATH@/components/dom_json.xpt
@RESPATH@/components/dom_power.xpt
@RESPATH@/components/dom_push.xpt
@RESPATH@/components/dom_quota.xpt
@RESPATH@/components/dom_range.xpt
@RESPATH@/components/dom_security.xpt
@ -558,7 +559,7 @@
@RESPATH@/components/AlarmsManager.manifest
@RESPATH@/components/Push.js
@RESPATH@/components/Push.manifest
@RESPATH@/components/PushServiceLauncher.js
@RESPATH@/components/PushNotificationService.js
@RESPATH@/components/SlowScriptDebug.manifest
@RESPATH@/components/SlowScriptDebug.js

Просмотреть файл

@ -398,6 +398,7 @@ safebrowsing.notAForgeryButton.accessKey=F
safebrowsing.reportedAttackSite=Reported Attack Site!
safebrowsing.notAnAttackButton.label=This isn't an attack site…
safebrowsing.notAnAttackButton.accessKey=A
safebrowsing.reportedUnwantedSite=Reported Unwanted Software Site!
# Ctrl-Tab
# LOCALIZATION NOTE (ctrlTab.listAllTabs.label): #1 represents the number

Просмотреть файл

@ -12,6 +12,11 @@
<!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
<!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
<!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
<!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
<!ENTITY safeb.blocked.unwantedPage.longDesc "<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>">
<!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
<!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
<!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">

Просмотреть файл

@ -30,6 +30,7 @@ externalProtocolUnknown=<Unknown>
externalProtocolChkMsg=Remember my choice for all links of this type.
externalProtocolLaunchBtn=Launch application
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.

Просмотреть файл

@ -164,6 +164,11 @@ be temporary, and you can try again later.</li>
<p>Website owners who believe their site has been reported as an attack site in error may <a href='http://www.stopbadware.org/home/reviewinfo' >request a review</a>.</p>
">
<!ENTITY unwantedBlocked.title "Suspected Unwanted Software Site!">
<!ENTITY unwantedBlocked.longDesc "
<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>
">
<!ENTITY phishingBlocked.title "Suspected Web Forgery!">
<!ENTITY phishingBlocked.longDesc "
<p>Entering any personal information on this page may result in identity theft or other fraud.</p>

Просмотреть файл

@ -341,20 +341,15 @@ let Content = {
}
} else if (/^about:blocked/.test(errorDoc.documentURI)) {
// The event came from a button on a malware/phishing block page
// First check whether it's malware or phishing, so that we can
// use the right strings/links.
let isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
if (ot == errorDoc.getElementById("getMeOutButton")) {
sendAsyncMessage("Browser:BlockedSite",
{ url: errorDoc.location.href, action: "leave" });
} else if (ot == errorDoc.getElementById("reportButton")) {
// This is the "Why is this site blocked" button. For malware,
// we can fetch a site-specific report, for phishing, we redirect
// to the generic page describing phishing protection.
let action = isMalware ? "report-malware" : "report-phishing";
// This is the "Why is this site blocked" button. We redirect
// to the generic page describing phishing/malware protection.
sendAsyncMessage("Browser:BlockedSite",
{ url: errorDoc.location.href, action: action });
{ url: errorDoc.location.href, action: "report-phishing" });
} else if (ot == errorDoc.getElementById("ignoreWarningButton")) {
// Allow users to override and continue through to the site,
// but add a notify bar as a reminder, so that they don't lose

Просмотреть файл

@ -240,6 +240,10 @@ let SelfSupportBackendInternal = {
// Fetch the Self Support URL from the preferences.
let unformattedURL = Preferences.get(PREF_URL, null);
let url = Services.urlFormatter.formatURL(unformattedURL);
if (!url.startsWith("https:")) {
this._log.error("_loadSelfSupport - Non HTTPS URL provided: " + url);
return;
}
this._log.config("_loadSelfSupport - URL " + url);

Просмотреть файл

@ -18,6 +18,7 @@ const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
const TEST_WAIT_RETRIES = 60;
const TEST_PAGE_URL = getRootDirectory(gTestPath) + "uitour.html";
const TEST_PAGE_URL_HTTPS = TEST_PAGE_URL.replace("chrome://mochitests/content/", "https://example.com/");
/**
* Find a browser, with an IFRAME as parent, who has aURL as the source attribute.
@ -104,9 +105,14 @@ add_task(function* setupEnvironment() {
// is enabled.
Preferences.set(PREF_SELFSUPPORT_ENABLED, true);
Preferences.set(PREF_UITOUR_ENABLED, true);
Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL);
Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL_HTTPS);
// Whitelist the HTTPS page to use UITour.
let pageURI = Services.io.newURI(TEST_PAGE_URL_HTTPS, null, null);
Services.perms.add(pageURI, "uitour", Services.perms.ALLOW_ACTION);
registerCleanupFunction(() => {
Services.perms.remove("example.com", "uitour");
Preferences.set(PREF_SELFSUPPORT_ENABLED, selfSupportEnabled);
Preferences.set(PREF_UITOUR_ENABLED, uitourEnabled);
Preferences.set(PREF_SELFSUPPORT_URL, selfSupportURL);
@ -126,7 +132,7 @@ add_task(function* test_selfSupport() {
// Wait for the SelfSupport page to load.
info("Waiting for the SelfSupport local page to load.");
let selfSupportBrowser = yield promiseSelfSupportLoad(TEST_PAGE_URL);
let selfSupportBrowser = yield promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
Assert.ok(!!selfSupportBrowser, "SelfSupport browser must exist.");
// Get a reference to the UITour API.
@ -146,13 +152,34 @@ add_task(function* test_selfSupport() {
// Wait until SelfSupport closes.
info("Waiting for the SelfSupport to close.");
yield promiseSelfSupportClose(TEST_PAGE_URL);
yield promiseSelfSupportClose(TEST_PAGE_URL_HTTPS);
// Find the SelfSupport browser, again. We don't expect to find it.
selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL);
selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL_HTTPS);
Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
// We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
// doesn't create any problem.
SelfSupportBackend.uninit();
});
/**
* Test that SelfSupportBackend only allows HTTPS.
*/
add_task(function* test_selfSupport_noHTTPS() {
Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL);
SelfSupportBackend.init();
// SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
info("Sending sessionstore-windows-restored");
Services.obs.notifyObservers(null, "sessionstore-windows-restored", null);
// Find the SelfSupport browser. We don't expect to find it since we are not using https.
let selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL);
Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
// We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
// doesn't create any problem.
SelfSupportBackend.uninit();
})

Просмотреть файл

@ -9,10 +9,8 @@ scrollbar {
padding: 2px;
}
/* Scrollbar code will reset the margin to the correct side depending on
layout.scrollbar.side pref */
scrollbar[orient="vertical"] {
margin-left: -10px;
-moz-margin-start: -10px;
min-width: 10px;
max-width: 10px;
}

Просмотреть файл

@ -10,10 +10,8 @@ scrollbar {
padding: 2px;
}
/* Scrollbar code will reset the margin to the correct side depending on
layout.scrollbar.side pref */
scrollbar[orient="vertical"] {
margin-left: -8px;
-moz-margin-start: -8px;
min-width: 8px;
max-width: 8px;
}

Просмотреть файл

@ -17,6 +17,7 @@
%define menuStateHover :not(:-moz-any([disabled],:active))[_moz-menuactive]
%define buttonStateActive :not([disabled]):-moz-any([open],:hover:active)
%define menuStateActive :not([disabled])[_moz-menuactive]:active
%define menuStateMenuActive :not([disabled])[_moz-menuactive]
%include ../browser.inc

Просмотреть файл

@ -233,13 +233,13 @@ menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
color: ButtonText;
}
.subviewbutton@menuStateHover@,
.subviewbutton@menuStateMenuActive@,
menuitem.panel-subview-footer@menuStateHover@,
.subviewbutton.panel-subview-footer@buttonStateHover@,
.subviewbutton.panel-subview-footer@buttonStateActive@,
.subviewbutton@menuStateHover@ > .menu-accel-container,
.PanelUI-subView .subviewbutton[shortcut]@buttonStateHover@::after,
#BMB_bookmarksPopup .panel-subview-footer@menuStateHover@ > .menu-text {
#BMB_bookmarksPopup .panel-subview-footer@menuStateMenuActive@ > .menu-text {
background-color: Highlight;
color: highlighttext !important;
}

Просмотреть файл

@ -9,10 +9,8 @@ scrollbar {
padding: 2px;
}
/* Scrollbar code will reset the margin to the correct side depending on
layout.scrollbar.side pref */
scrollbar[orient="vertical"] {
margin-left: -10px;
-moz-margin-start: -10px;
min-width: 10px;
max-width: 10px;
}

Просмотреть файл

@ -101,12 +101,15 @@ def processSingleLeakFile(leakLogFileName, processType, leakThreshold, ignoreMis
"""Process a single leak log.
"""
# Per-Inst Leaked Total Rem ...
# 0 TOTAL 17 192 419115886 2 ...
# 833 nsTimerImpl 60 120 24726 2 ...
lineRe = re.compile(r"^\s*\d+\s+(?P<name>\S+)\s+"
r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
r"-?\d+\s+(?P<numLeaked>-?\d+)")
# | |Per-Inst Leaked| Total Rem|
# 0 |TOTAL | 17 192| 419115886 2|
# 833 |nsTimerImpl | 60 120| 24726 2|
# 930 |Foo<Bar, Bar> | 32 8| 100 1|
lineRe = re.compile(r"^\s*\d+ \|"
r"(?P<name>[^|]+)\|"
r"\s*(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s*\|"
r"\s*-?\d+\s+(?P<numLeaked>-?\d+)")
# The class name can contain spaces. We remove trailing whitespace later.
processString = "%s process:" % processType
crashedOnPurpose = False
@ -125,7 +128,7 @@ def processSingleLeakFile(leakLogFileName, processType, leakThreshold, ignoreMis
# eg: the leak table header row
log.info(line.rstrip())
continue
name = matches.group("name")
name = matches.group("name").rstrip()
size = int(matches.group("size"))
bytesLeaked = int(matches.group("bytesLeaked"))
numLeaked = int(matches.group("numLeaked"))

Просмотреть файл

@ -84,6 +84,11 @@ private:
virtual void run(const MatchFinder::MatchResult &Result);
};
class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
ScopeChecker stackClassChecker;
ScopeChecker globalClassChecker;
NonHeapClassChecker nonheapClassChecker;
@ -92,16 +97,17 @@ private:
NaNExprChecker nanExprChecker;
NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
MatchFinder astMatcher;
};
namespace {
bool isInIgnoredNamespace(const Decl *decl) {
std::string getDeclarationNamespace(const Decl *decl) {
const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext();
const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
if (!ND) {
return false;
return "";
}
while (const DeclContext *ParentDC = ND->getParent()) {
@ -112,8 +118,15 @@ bool isInIgnoredNamespace(const Decl *decl) {
}
const auto& name = ND->getName();
return name;
}
bool isInIgnoredNamespaceForImplicitCtor(const Decl *decl) {
std::string name = getDeclarationNamespace(decl);
if (name == "") {
return false;
}
// namespace std and icu are ignored for now
return name == "std" || // standard C++ lib
name == "__gnu_cxx" || // gnu C++ lib
name == "boost" || // boost
@ -129,7 +142,19 @@ bool isInIgnoredNamespace(const Decl *decl) {
name == "testing"; // gtest
}
bool isIgnoredPath(const Decl *decl) {
bool isInIgnoredNamespaceForImplicitConversion(const Decl *decl) {
std::string name = getDeclarationNamespace(decl);
if (name == "") {
return false;
}
return name == "std" || // standard C++ lib
name == "__gnu_cxx" || // gnu C++ lib
name == "google_breakpad" || // breakpad
name == "testing"; // gtest
}
bool isIgnoredPathForImplicitCtor(const Decl *decl) {
decl = decl->getCanonicalDecl();
SourceLocation Loc = decl->getLocation();
const SourceManager &SM = decl->getASTContext().getSourceManager();
@ -150,9 +175,30 @@ bool isIgnoredPath(const Decl *decl) {
return false;
}
bool isInterestingDecl(const Decl *decl) {
return !isInIgnoredNamespace(decl) &&
!isIgnoredPath(decl);
bool isIgnoredPathForImplicitConversion(const Decl *decl) {
decl = decl->getCanonicalDecl();
SourceLocation Loc = decl->getLocation();
const SourceManager &SM = decl->getASTContext().getSourceManager();
SmallString<1024> FileName = SM.getFilename(Loc);
llvm::sys::fs::make_absolute(FileName);
llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
end = llvm::sys::path::rend(FileName);
for (; begin != end; ++begin) {
if (begin->compare_lower(StringRef("graphite2")) == 0) {
return true;
}
}
return false;
}
bool isInterestingDeclForImplicitCtor(const Decl *decl) {
return !isInIgnoredNamespaceForImplicitCtor(decl) &&
!isIgnoredPathForImplicitCtor(decl);
}
bool isInterestingDeclForImplicitConversion(const Decl *decl) {
return !isInIgnoredNamespaceForImplicitConversion(decl) &&
!isIgnoredPathForImplicitConversion(decl);
}
}
@ -232,7 +278,7 @@ public:
}
}
if (!d->isAbstract() && isInterestingDecl(d)) {
if (!d->isAbstract() && isInterestingDeclForImplicitCtor(d)) {
for (CXXRecordDecl::ctor_iterator ctor = d->ctor_begin(),
e = d->ctor_end(); ctor != e; ++ctor) {
// Ignore non-converting ctors
@ -416,6 +462,16 @@ bool isClassRefCounted(QualType T) {
return clazz ? isClassRefCounted(clazz) : RegularClass;
}
template<class T>
bool IsInSystemHeader(const ASTContext &AC, const T &D) {
auto &SourceManager = AC.getSourceManager();
auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
if (ExpansionLoc.isInvalid()) {
return false;
}
return SourceManager.isInSystemHeader(ExpansionLoc);
}
}
namespace clang {
@ -515,12 +571,7 @@ AST_MATCHER(QualType, isFloat) {
/// isExpansionInSystemHeader in newer clangs, but modified in order to work
/// with old clangs that we use on infra.
AST_MATCHER(BinaryOperator, isInSystemHeader) {
auto &SourceManager = Finder->getASTContext().getSourceManager();
auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
if (ExpansionLoc.isInvalid()) {
return false;
}
return SourceManager.isInSystemHeader(ExpansionLoc);
return IsInSystemHeader(Finder->getASTContext(), Node);
}
/// This matcher will match locations in SkScalar.h. This header contains a
@ -649,6 +700,13 @@ DiagnosticsMatcher::DiagnosticsMatcher()
hasDescendant(declRefExpr(hasType(pointerType(pointee(isRefCounted())))).bind("node"))
),
&refCountedInsideLambdaChecker);
// Older clang versions such as the ones used on the infra recognize these
// conversions as 'operator _Bool', but newer clang versions recognize these
// as 'operator bool'.
astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"),
hasName("operator _Bool"))).bind("node"),
&explicitOperatorBoolChecker);
}
void DiagnosticsMatcher::ScopeChecker::run(
@ -861,6 +919,25 @@ void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run(
Diag.Report(node->getLocStart(), noteID);
}
void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "bad implicit conversion operator for %0");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "consider adding the explicit keyword to %0");
const CXXConversionDecl *method = Result.Nodes.getNodeAs<CXXConversionDecl>("node");
const CXXRecordDecl *clazz = method->getParent();
if (!method->isExplicitSpecified() &&
!MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
!IsInSystemHeader(method->getASTContext(), *method) &&
isInterestingDeclForImplicitConversion(method)) {
Diag.Report(method->getLocStart(), errorID) << clazz;
Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
}
}
class MozCheckAction : public PluginASTAction {
public:
ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override {

Просмотреть файл

@ -0,0 +1,11 @@
#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit")))
struct Bad {
operator bool(); // expected-error {{bad implicit conversion operator for 'Bad'}} expected-note {{consider adding the explicit keyword to 'operator bool'}}
};
struct Good {
explicit operator bool();
};
struct Okay {
MOZ_IMPLICIT operator bool();
};

Просмотреть файл

@ -7,6 +7,7 @@
SOURCES += [
'TestBadImplicitConversionCtor.cpp',
'TestCustomHeap.cpp',
'TestExplicitOperatorBool.cpp',
'TestGlobalClass.cpp',
'TestMustOverride.cpp',
'TestNANTestingExpr.cpp',

Просмотреть файл

@ -180,7 +180,13 @@ class RemoteAutomation(Automation):
self.checkForANRs()
self.checkForTombstones()
logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
try:
logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
except DMError:
print "getLogcat threw DMError; re-trying just once..."
time.sleep(1)
logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
javaException = mozcrash.check_for_java_exception(logcat)
if javaException:
return True

Просмотреть файл

@ -8,7 +8,7 @@
# It is referred to by the following page, so if this file moves, that page must
# be modified accordingly:
#
# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
# https://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
#
# Empty lines and lines which begin with "#" are ignored and may be used for
# storing comments. All other lines consist of an origin followed by whitespace

Просмотреть файл

@ -13,10 +13,6 @@
namespace mozilla {
namespace dom {
class nsIContentParent;
};
namespace ipc {
class URIParams;
};

Просмотреть файл

@ -9,8 +9,6 @@
#include "jsapi.h"
#include "nsIPrincipal.h"
class nsCString;
struct nsJSPrincipals : nsIPrincipal, JSPrincipals
{
static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other);

Просмотреть файл

@ -15,9 +15,6 @@
#include "nsNetUtil.h"
#include "nsScriptSecurityManager.h"
class nsIObjectInputStream;
class nsIObjectOutputStream;
class nsBasePrincipal : public nsJSPrincipals
{
public:

Просмотреть файл

@ -17,13 +17,10 @@
#include <stdint.h>
class nsIDocShell;
class nsCString;
class nsIClassInfo;
class nsIIOService;
class nsIStringBundle;
class nsSystemPrincipal;
class ClassInfoData;
/////////////////////////////
// nsScriptSecurityManager //

Просмотреть файл

@ -3017,6 +3017,7 @@ then
esac
LDFLAGS="${_PTHREAD_LDFLAGS} ${LDFLAGS}"
AC_SUBST(MOZ_USE_PTHREADS)
MOZ_CHECK_HEADERS(pthread.h)
fi

Просмотреть файл

@ -10,7 +10,6 @@
#include "nsISupports.h"
class nsIURI;
class nsString;
namespace mozilla {

Просмотреть файл

@ -14,8 +14,6 @@
#include "nsIInterfaceRequestor.h"
#include "nsILoadContext.h"
class mozIApplication;
namespace mozilla {
/**

Просмотреть файл

@ -5087,7 +5087,8 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
}
}
} else if (NS_ERROR_PHISHING_URI == aError ||
NS_ERROR_MALWARE_URI == aError) {
NS_ERROR_MALWARE_URI == aError ||
NS_ERROR_UNWANTED_URI == aError) {
nsAutoCString host;
aURI->GetHost(host);
CopyUTF8toUTF16(host, formatStrs[0]);
@ -5106,14 +5107,19 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
error.AssignLiteral("phishingBlocked");
bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
: nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
} else {
} else if (NS_ERROR_MALWARE_URI == aError) {
error.AssignLiteral("malwareBlocked");
bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
: nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
} else {
error.AssignLiteral("unwantedBlocked");
bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
: nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
}
if (errorPage.EqualsIgnoreCase("blocked"))
if (errorPage.EqualsIgnoreCase("blocked")) {
Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId);
}
cssClass.AssignLiteral("blacklist");
} else if (NS_ERROR_CONTENT_CRASHED == aError) {
@ -7824,6 +7830,7 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
aStatus == NS_ERROR_OFFLINE ||
aStatus == NS_ERROR_MALWARE_URI ||
aStatus == NS_ERROR_PHISHING_URI ||
aStatus == NS_ERROR_UNWANTED_URI ||
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
aStatus == NS_ERROR_REMOTE_XUL ||
aStatus == NS_ERROR_OFFLINE ||

Просмотреть файл

@ -88,7 +88,6 @@ class nsIURIFixup;
class nsIURILoader;
class nsIWebBrowserFind;
class nsIWidget;
class ProfilerMarkerTracing;
/* load commands were moved to nsIDocShell.h */
/* load types were moved to nsDocShellLoadTypes.h */

Просмотреть файл

@ -6,7 +6,9 @@
#include "nsISupports.idl"
[scriptable, uuid(AF13EA3A-D488-4308-B843-526E055AB943)]
interface nsIDOMNode;
[scriptable, uuid(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)]
interface nsIContentViewerEdit : nsISupports
{
void clearSelection();
@ -27,4 +29,8 @@ interface nsIContentViewerEdit : nsISupports
AString getContents(in string aMimeType, in boolean aSelectionOnly);
readonly attribute boolean canGetContents;
// Set the node that will be the subject of the editing commands above.
// Usually this will be the node that was context-clicked.
void setCommandNode(in nsIDOMNode aNode);
};

Просмотреть файл

@ -13,7 +13,6 @@ class nsIContent;
class nsIDocShell;
class nsIInputStream;
class nsIRequest;
class nsString;
// Interface ID for nsILinkHandler
#define NS_ILINKHANDLER_IID \

Просмотреть файл

@ -291,6 +291,7 @@
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
<h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
@ -317,6 +318,7 @@
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
<div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>

Просмотреть файл

@ -217,6 +217,32 @@ Animation::GetFinished(ErrorResult& aRv)
return mFinished;
}
void
Animation::Finish(ErrorResult& aRv)
{
// https://w3c.github.io/web-animations/#finish-an-animation
if (mPlaybackRate == 0 ||
(mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
TimeDuration limit =
mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
SetCurrentTime(limit);
if (mPendingState == PendingState::PlayPending) {
CancelPendingTasks();
if (mReady) {
mReady->MaybeResolve(this);
}
}
UpdateFinishedState(true);
PostUpdate();
}
void
Animation::Play(LimitBehavior aLimitBehavior)
{

Просмотреть файл

@ -94,6 +94,7 @@ public:
AnimationPlayState PlayState() const;
virtual Promise* GetReady(ErrorResult& aRv);
virtual Promise* GetFinished(ErrorResult& aRv);
virtual void Finish(ErrorResult& aRv);
virtual void Play(LimitBehavior aLimitBehavior);
virtual void Pause();
bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }

Просмотреть файл

@ -83,112 +83,6 @@ const TEN_PCT_POSITION = 110;
const FIFTY_PCT_POSITION = 150;
const END_POSITION = 200;
/**
* CSS animation events fire asynchronously after we set 'startTime'. This
* helper class allows us to handle such events using Promises.
*
* To use this class:
*
* var eventWatcher = new EventWatcher(watchedNode, eventTypes);
* eventWatcher.waitForEvent(eventType).then(function() {
* // Promise fulfilled
* checkStuff();
* makeSomeChanges();
* return eventWatcher.waitForEvent(nextEventType);
* }).then(function() {
* // Promise fulfilled
* checkMoreStuff();
* eventWatcher.stopWatching(); // all done - stop listening for events
* });
*
* This class will assert_unreached() if an event occurs when there is no
* Promise created by a waitForEvent() call waiting to be fulfilled, or if the
* event is of a different type to the type passed to waitForEvent. This helps
* provide test coverage to ensure that only events that are expected occur, in
* the correct order and with the correct timing. It also helps vastly simplify
* the already complex code below by avoiding lots of gnarly error handling
* code.
*/
function EventWatcher(watchedNode, eventTypes)
{
if (typeof eventTypes == 'string') {
eventTypes = [eventTypes];
}
var waitingFor = null;
function eventHandler(evt) {
if (!waitingFor) {
assert_unreached('Not expecting event, but got: ' + evt.type +
' targeting element #' + evt.target.getAttribute('id'));
return;
}
if (evt.type != waitingFor.types[0]) {
assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
evt.type + ' event');
return;
}
if (waitingFor.types.length > 1) {
// Pop first event from array
waitingFor.types.shift();
return;
}
// We need to null out waitingFor before calling the resolve function since
// the Promise's resolve handlers may call waitForEvent() which will need
// to set waitingFor.
var resolveFunc = waitingFor.resolve;
waitingFor = null;
resolveFunc(evt);
}
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.addEventListener(eventTypes[i], eventHandler);
}
this.waitForEvent = function(type) {
if (typeof type != 'string') {
return Promise.reject('Event type not a string');
}
return this.waitForEvents([type]);
};
/**
* This is useful when two events are expected to fire one immediately after
* the other. This happens when we skip over the entire active interval for
* instance. In this case an 'animationstart' and an 'animationend' are fired
* and due to the asynchronous nature of Promise callbacks this won't work:
*
* eventWatcher.waitForEvent('animationstart').then(function() {
* return waitForEvent('animationend');
* }).then(...);
*
* It doesn't work because the 'animationend' listener is added too late,
* because the resolve handler for the first Promise is called asynchronously
* some time after the 'animationstart' event is called, rather than at the
* time the event reaches the watched element.
*/
this.waitForEvents = function(types) {
if (waitingFor) {
return Promise.reject('Already waiting for an event');
}
return new Promise(function(resolve, reject) {
waitingFor = {
types: types,
resolve: resolve,
reject: reject
};
});
};
this.stopWatching = function() {
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.removeEventListener(eventTypes[i], eventHandler);
}
};
return this;
}
// The terms used for the naming of the following helper functions refer to
// terms used in the Web Animations specification for specific phases of an
// animation. The terms can be found here:
@ -336,7 +230,7 @@ test(function(t)
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
@ -347,7 +241,7 @@ async_test(function(t) {
animation.currentTime =
currentTimeForStartOfActiveInterval(animation.timeline);
return eventWatcher.waitForEvent('animationstart');
return eventWatcher.wait_for('animationstart');
})).then(t.step_func(function() {
checkStateAtActiveIntervalStartTime(animation);
@ -357,11 +251,9 @@ async_test(function(t) {
animation.currentTime =
currentTimeForEndOfActiveInterval(animation.timeline);
return eventWatcher.waitForEvent('animationend');
return eventWatcher.wait_for('animationend');
})).then(t.step_func(function() {
checkStateAtActiveIntervalEndTime(animation);
eventWatcher.stopWatching();
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
@ -372,7 +264,7 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
@ -389,8 +281,8 @@ async_test(function(t) {
// an 'animationend' event. We need to wait for these events before we start
// testing going backwards since EventWatcher will fail the test if it gets
// an event that we haven't told it about.
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(t.step_func(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(t.step_func(function() {
assert_true(document.timeline.currentTime - previousTimelineTime <
ANIM_DUR_MS,
'Sanity check that seeking worked rather than the events ' +
@ -410,9 +302,9 @@ async_test(function(t) {
//
// Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
// causing computed style to be updated and the 'animationstart' event to
// be dispatched synchronously. We need to call waitForEvent first
// be dispatched synchronously. We need to call wait_for first
// otherwise eventWatcher will assert that the event was unexpected.
var promise = eventWatcher.waitForEvent('animationstart');
var promise = eventWatcher.wait_for('animationstart');
checkStateAtFiftyPctOfActiveInterval(animation);
return promise;
})).then(t.step_func(function() {
@ -424,11 +316,9 @@ async_test(function(t) {
// Despite going backwards from just after the active interval starts to
// the animation start time, we now expect an animationend event
// because we went from inside to outside the active interval.
return eventWatcher.waitForEvent('animationend');
return eventWatcher.wait_for('animationend');
})).then(t.step_func(function() {
checkStateOnReadyPromiseResolved(animation);
eventWatcher.stopWatching();
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
@ -454,7 +344,7 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
@ -462,14 +352,13 @@ async_test(function(t) {
animation.currentTime = currentTimeForBeforePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
}, 'Redundant change, before -> active, then back');
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
@ -477,23 +366,21 @@ async_test(function(t) {
animation.currentTime = currentTimeForBeforePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
}, 'Redundant change, before -> after, then back');
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvent('animationstart').then(function() {
eventWatcher.wait_for('animationstart').then(function() {
animation.currentTime = currentTimeForBeforePhase(animation.timeline);
animation.currentTime = currentTimeForActivePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -503,16 +390,15 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvent('animationstart').then(function() {
eventWatcher.wait_for('animationstart').then(function() {
animation.currentTime = currentTimeForAfterPhase(animation.timeline);
animation.currentTime = currentTimeForActivePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -522,17 +408,16 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(function() {
animation.currentTime = currentTimeForBeforePhase(animation.timeline);
animation.currentTime = currentTimeForAfterPhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -542,17 +427,16 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(function() {
animation.currentTime = currentTimeForActivePhase(animation.timeline);
animation.currentTime = currentTimeForAfterPhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});

Просмотреть файл

@ -0,0 +1,160 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
.animated-div {
margin-left: 10px;
}
@keyframes anim {
from { margin-left: 100px; }
to { margin-left: 200px; }
}
</style>
<script>
'use strict';
const ANIM_PROP_VAL = 'anim 100s';
const ANIM_DURATION = 100000; // ms
test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.playbackRate = 0;
var threw = false;
try {
animation.finish();
} catch (e) {
threw = true;
assert_equals(e.name, 'InvalidStateError',
'Exception should be an InvalidStateError exception when ' +
'trying to finish an animation with playbackRate == 0');
}
assert_true(threw,
'Expect InvalidStateError exception trying to finish an ' +
'animation with playbackRate == 0');
}, 'Test exceptions when finishing non-running animation');
test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
div.style.animationIterationCount = 'infinite';
var animation = div.getAnimations()[0];
var threw = false;
try {
animation.finish();
} catch (e) {
threw = true;
assert_equals(e.name, 'InvalidStateError',
'Exception should be an InvalidStateError exception when ' +
'trying to finish an infinite animation');
}
assert_true(threw,
'Expect InvalidStateError exception trying to finish an ' +
'infinite animation');
}, 'Test exceptions when finishing infinite animation');
test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.finish();
assert_equals(animation.currentTime, ANIM_DURATION,
'After finishing, the currentTime should be set to the end ' +
'of the active duration');
}, 'Test finishing of animation');
test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.currentTime = ANIM_DURATION + 1000; // 1s past effect end
animation.finish();
assert_equals(animation.currentTime, ANIM_DURATION,
'After finishing, the currentTime should be set back to the ' +
'end of the active duration');
}, 'Test finishing of animation with a current time past the effect end');
async_test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.currentTime = ANIM_DURATION;
animation.finished.then(t.step_func(function() {
animation.playbackRate = -1;
animation.finish();
assert_equals(animation.currentTime, 0,
'After finishing a reversed animation the currentTime ' +
'should be set to zero');
t.done();
}));
}, 'Test finishing of reversed animation');
async_test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.currentTime = ANIM_DURATION;
animation.finished.then(t.step_func(function() {
animation.playbackRate = -1;
animation.currentTime = -1000;
animation.finish();
assert_equals(animation.currentTime, 0,
'After finishing a reversed animation the currentTime ' +
'should be set back to zero');
t.done();
}));
}, 'Test finishing of reversed animation with with a current time less ' +
'than zero');
async_test(function(t) {
var div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.pause();
animation.ready.then(t.step_func(function() {
animation.finish();
assert_equals(animation.playState, 'paused',
'The play state of a paused animation should remain ' +
'"paused" even after finish() is called');
t.done();
}));
}, 'Test paused state after finishing of animation');
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
div.style.animation = ANIM_PROP_VAL;
var animation = div.getAnimations()[0];
animation.ready.then(t.step_func(function() {
animation.finish();
var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
assert_equals(marginLeft, 10,
'The computed style should be reset when finish() is ' +
'called');
t.done();
}));
}, 'Test resetting of computed style');
</script>

Просмотреть файл

@ -77,7 +77,7 @@ async_test(function(t) {
animation.currentTime = ANIM_DURATION;
animation.finished.then(function() {
animation.finished.then(t.step_func(function() {
previousFinishedPromise = animation.finished;
animation.playbackRate = -1;
assert_not_equals(animation.finished, previousFinishedPromise,
@ -85,7 +85,7 @@ async_test(function(t) {
'finished promise');
animation.currentTime = 0;
return animation.finished;
}).then(t.step_func(function() {
})).then(t.step_func(function() {
previousFinishedPromise = animation.finished;
animation.play();
assert_not_equals(animation.finished, previousFinishedPromise,

Просмотреть файл

@ -44,13 +44,13 @@ async_test(function(t) {
var animation = div.getAnimations()[0];
var originalReadyPromise = animation.ready;
animation.ready.then(function() {
animation.ready.then(t.step_func(function() {
div.style.animationPlayState = 'running';
assert_not_equals(animation.ready, originalReadyPromise,
'After updating animation-play-state a new ready promise'
+ ' object is created');
t.done();
});
}));
}, 'A new ready promise is created when setting animation-play-state: running');
async_test(function(t) {
@ -58,14 +58,14 @@ async_test(function(t) {
div.style.animation = 'abc 100s';
var animation = div.getAnimations()[0];
animation.ready.then(function() {
animation.ready.then(t.step_func(function() {
var promiseBeforeCallingPlay = animation.ready;
animation.play();
assert_equals(animation.ready, promiseBeforeCallingPlay,
'Ready promise has same object identity after redundant call'
+ ' to play()');
t.done();
});
}));
}, 'Redundant calls to play() do not generate new ready promise objects');
async_test(function(t) {
@ -73,12 +73,12 @@ async_test(function(t) {
div.style.animation = 'abc 100s';
var animation = div.getAnimations()[0];
animation.ready.then(function(resolvedAnimation) {
animation.ready.then(t.step_func(function(resolvedAnimation) {
assert_equals(resolvedAnimation, animation,
'Object identity of Animation passed to Promise callback'
+ ' matches the Animation object owning the Promise');
t.done();
});
}));
}, 'The ready promise is fulfilled with its Animation');
async_test(function(t) {

Просмотреть файл

@ -83,112 +83,6 @@ const TEN_PCT_POSITION = 110;
const FIFTY_PCT_POSITION = 150;
const END_POSITION = 200;
/**
* CSS animation events fire asynchronously after we set 'startTime'. This
* helper class allows us to handle such events using Promises.
*
* To use this class:
*
* var eventWatcher = new EventWatcher(watchedNode, eventTypes);
* eventWatcher.waitForEvent(eventType).then(function() {
* // Promise fulfilled
* checkStuff();
* makeSomeChanges();
* return eventWatcher.waitForEvent(nextEventType);
* }).then(function() {
* // Promise fulfilled
* checkMoreStuff();
* eventWatcher.stopWatching(); // all done - stop listening for events
* });
*
* This class will assert_unreached() if an event occurs when there is no
* Promise created by a waitForEvent() call waiting to be fulfilled, or if the
* event is of a different type to the type passed to waitForEvent. This helps
* provide test coverage to ensure that only events that are expected occur, in
* the correct order and with the correct timing. It also helps vastly simplify
* the already complex code below by avoiding lots of gnarly error handling
* code.
*/
function EventWatcher(watchedNode, eventTypes)
{
if (typeof eventTypes == 'string') {
eventTypes = [eventTypes];
}
var waitingFor = null;
function eventHandler(evt) {
if (!waitingFor) {
assert_unreached('Not expecting event, but got: ' + evt.type +
' targeting element #' + evt.target.getAttribute('id'));
return;
}
if (evt.type != waitingFor.types[0]) {
assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
evt.type + ' event');
return;
}
if (waitingFor.types.length > 1) {
// Pop first event from array
waitingFor.types.shift();
return;
}
// We need to null out waitingFor before calling the resolve function since
// the Promise's resolve handlers may call waitForEvent() which will need
// to set waitingFor.
var resolveFunc = waitingFor.resolve;
waitingFor = null;
resolveFunc(evt);
}
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.addEventListener(eventTypes[i], eventHandler);
}
this.waitForEvent = function(type) {
if (typeof type != 'string') {
return Promise.reject('Event type not a string');
}
return this.waitForEvents([type]);
};
/**
* This is useful when two events are expected to fire one immediately after
* the other. This happens when we skip over the entire active interval for
* instance. In this case an 'animationstart' and an 'animationend' are fired
* and due to the asynchronous nature of Promise callbacks this won't work:
*
* eventWatcher.waitForEvent('animationstart').then(function() {
* return waitForEvent('animationend');
* }).then(...);
*
* It doesn't work because the 'animationend' listener is added too late,
* because the resolve handler for the first Promise is called asynchronously
* some time after the 'animationstart' event is called, rather than at the
* time the event reaches the watched element.
*/
this.waitForEvents = function(types) {
if (waitingFor) {
return Promise.reject('Already waiting for an event');
}
return new Promise(function(resolve, reject) {
waitingFor = {
types: types,
resolve: resolve,
reject: reject
};
});
};
this.stopWatching = function() {
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.removeEventListener(eventTypes[i], eventHandler);
}
};
return this;
}
// The terms used for the naming of the following helper functions refer to
// terms used in the Web Animations specification for specific phases of an
// animation. The terms can be found here:
@ -390,7 +284,7 @@ test(function(t)
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
@ -400,7 +294,7 @@ async_test(function(t) {
checkStateOnReadyPromiseResolved(animation);
animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
return eventWatcher.waitForEvent('animationstart');
return eventWatcher.wait_for('animationstart');
})).then(t.step_func(function() {
checkStateAtActiveIntervalStartTime(animation);
@ -409,11 +303,9 @@ async_test(function(t) {
checkStateAtFiftyPctOfActiveInterval(animation);
animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
return eventWatcher.waitForEvent('animationend');
return eventWatcher.wait_for('animationend');
})).then(t.step_func(function() {
checkStateAtActiveIntervalEndTime(animation);
eventWatcher.stopWatching();
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
@ -424,7 +316,7 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
@ -438,8 +330,8 @@ async_test(function(t) {
// an 'animationend' event. We need to wait for these events before we start
// testing going backwards since EventWatcher will fail the test if it gets
// an event that we haven't told it about.
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(t.step_func(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(t.step_func(function() {
assert_true(document.timeline.currentTime - previousTimelineTime <
ANIM_DUR_MS,
'Sanity check that seeking worked rather than the events ' +
@ -459,9 +351,9 @@ async_test(function(t) {
//
// Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
// causing computed style to be updated and the 'animationstart' event to
// be dispatched synchronously. We need to call waitForEvent first
// be dispatched synchronously. We need to call wait_for first
// otherwise eventWatcher will assert that the event was unexpected.
var promise = eventWatcher.waitForEvent('animationstart');
var promise = eventWatcher.wait_for('animationstart');
checkStateAtFiftyPctOfActiveInterval(animation);
return promise;
})).then(t.step_func(function() {
@ -472,11 +364,9 @@ async_test(function(t) {
// Despite going backwards from just after the active interval starts to
// the animation start time, we now expect an animationend event
// because we went from inside to outside the active interval.
return eventWatcher.waitForEvent('animationend');
return eventWatcher.wait_for('animationend');
})).then(t.step_func(function() {
checkStateOnReadyPromiseResolved(animation);
eventWatcher.stopWatching();
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
@ -502,7 +392,7 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
@ -510,14 +400,13 @@ async_test(function(t) {
animation.startTime = startTimeForBeforePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
}, 'Redundant change, before -> active, then back');
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
@ -525,23 +414,21 @@ async_test(function(t) {
animation.startTime = startTimeForBeforePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
}, 'Redundant change, before -> after, then back');
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvent('animationstart').then(function() {
eventWatcher.wait_for('animationstart').then(function() {
animation.startTime = startTimeForBeforePhase(animation.timeline);
animation.startTime = startTimeForActivePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -551,16 +438,15 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvent('animationstart').then(function() {
eventWatcher.wait_for('animationstart').then(function() {
animation.startTime = startTimeForAfterPhase(animation.timeline);
animation.startTime = startTimeForActivePhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -570,17 +456,16 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(function() {
animation.startTime = startTimeForBeforePhase(animation.timeline);
animation.startTime = startTimeForAfterPhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -590,17 +475,16 @@ async_test(function(t) {
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
div.style.animation = ANIM_PROPERTY_VAL;
var animation = div.getAnimations()[0];
eventWatcher.waitForEvents(['animationstart',
'animationend']).then(function() {
eventWatcher.wait_for(['animationstart',
'animationend']).then(function() {
animation.startTime = startTimeForActivePhase(animation.timeline);
animation.startTime = startTimeForAfterPhase(animation.timeline);
waitForAnimationFrames(2).then(function() {
eventWatcher.stopWatching();
t.done();
});
});
@ -623,11 +507,11 @@ async_test(function(t) {
return animation.ready;
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
})).then(t.step_func(function() {
assert_equals(animation.currentTime, storedCurrentTime,
'Test that hold time is correct');
t.done();
});
}));
}, 'Setting startTime to null');

Просмотреть файл

@ -73,111 +73,6 @@ const TEN_PCT_POSITION = 110;
const FIFTY_PCT_POSITION = 150;
const END_POSITION = 200;
/**
* CSS animation events fire asynchronously after we set 'startTime'. This
* helper class allows us to handle such events using Promises.
*
* To use this class:
*
* var eventWatcher = new EventWatcher(watchedNode, eventTypes);
* eventWatcher.waitForEvent(eventType).then(function() {
* // Promise fulfilled
* checkStuff();
* makeSomeChanges();
* return eventWatcher.waitForEvent(nextEventType);
* }).then(function() {
* // Promise fulfilled
* checkMoreStuff();
* eventWatcher.stopWatching(); // all done - stop listening for events
* });
*
* This class will assert_unreached() if an event occurs when there is no
* Promise created by a waitForEvent() call waiting to be fulfilled, or if the
* event is of a different type to the type passed to waitForEvent. This helps
* provide test coverage to ensure that only events that are expected occur, in
* the correct order and with the correct timing. It also helps vastly simplify
* the already complex code below by avoiding lots of gnarly error handling
* code.
*/
function EventWatcher(watchedNode, eventTypes)
{
if (typeof eventTypes == 'string') {
eventTypes = [eventTypes];
}
var waitingFor = null;
function eventHandler(evt) {
if (!waitingFor) {
assert_unreached('Not expecting event, but got: ' + evt.type +
' targeting element #' + evt.target.getAttribute('id'));
return;
}
if (evt.type != waitingFor.types[0]) {
assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
evt.type + ' event');
return;
}
if (waitingFor.types.length > 1) {
// Pop first event from array
waitingFor.types.shift();
return;
}
// We need to null out waitingFor before calling the resolve function since
// the Promise's resolve handlers may call waitForEvent() which will need
// to set waitingFor.
var resolveFunc = waitingFor.resolve;
waitingFor = null;
resolveFunc(evt);
}
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.addEventListener(eventTypes[i], eventHandler);
}
this.waitForEvent = function(type) {
if (typeof type != 'string') {
return Promise.reject('Event type not a string');
}
return this.waitForEvents([type]);
};
/**
* This is useful when two events are expected to fire one immediately after
* the other. This happens when we skip over the entire active interval for
* instance. In this case an 'animationstart' and an 'animationend' are fired
* and due to the asynchronous nature of Promise callbacks this won't work:
*
* eventWatcher.waitForEvent('animationstart').then(function() {
* return waitForEvent('animationend');
* }).then(...);
*
* It doesn't work because the 'animationend' listener is added too late,
* because the resolve handler for the first Promise is called asynchronously
* some time after the 'animationstart' event is called, rather than at the
* time the event reaches the watched element.
*/
this.waitForEvents = function(types) {
if (waitingFor) {
return Promise.reject('Already waiting for an event');
}
return new Promise(function(resolve, reject) {
waitingFor = {
types: types,
resolve: resolve,
reject: reject
};
});
};
this.stopWatching = function() {
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.removeEventListener(eventTypes[i], eventHandler);
}
};
return this;
}
// The terms used for the naming of the following helper functions refer to
// terms used in the Web Animations specification for specific phases of an
@ -300,7 +195,7 @@ test(function(t)
async_test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, 'transitionend');
var eventWatcher = new EventWatcher(t, div, 'transitionend');
flushComputedStyle(div);
div.style.marginLeft = '200px'; // initiate transition
@ -317,11 +212,9 @@ async_test(function(t) {
checkStateAtFiftyPctOfActiveInterval(animation);
animation.currentTime = currentTimeForEndOfActiveInterval();
return eventWatcher.waitForEvent('transitionend');
return eventWatcher.wait_for('transitionend');
})).then(t.step_func(function() {
checkStateAtActiveIntervalEndTime(animation);
eventWatcher.stopWatching();
})).catch(t.step_func(function(reason) {
assert_unreached(reason);
})).then(function() {
@ -332,7 +225,7 @@ async_test(function(t) {
test(function(t) {
var div = addDiv(t, {'class': 'animated-div'});
var eventWatcher = new EventWatcher(div, 'transitionend');
var eventWatcher = new EventWatcher(t, div, 'transitionend');
flushComputedStyle(div);
div.style.marginLeft = '200px'; // initiate transition
@ -356,10 +249,9 @@ test(function(t) {
//
// Calling checkStateAtActiveIntervalStartTime will check computed style,
// causing computed style to be updated and the 'transitionend' event to
// be dispatched synchronously. We need to call waitForEvent first
// be dispatched synchronously. We need to call wait_for first
// otherwise eventWatcher will assert that the event was unexpected.
eventWatcher.waitForEvent('transitionend').then(function() {
eventWatcher.stopWatching();
eventWatcher.wait_for('transitionend').then(function() {
t.done();
});
checkStateAtActiveIntervalStartTime(animation);

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше