merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-08-05 12:54:01 +02:00
commit 49824a8b10
102 changed files with 2533 additions and 1182 deletions

View File

@ -358,6 +358,8 @@ function init() {
}
break;
}
}).catch(err => {
error("Failed to get the signed in user: " + err);
});
}

View File

@ -33,6 +33,7 @@ let gFxAccounts = {
"weave:service:setup-complete",
"weave:ui:login:error",
"fxa-migration:state-changed",
this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
this.FxAccountsCommon.ONVERIFIED_NOTIFICATION,
this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
"weave:notification:removed",
@ -222,10 +223,11 @@ let gFxAccounts = {
this.updateMigrationNotification();
},
// Note that updateAppMenuItem() returns a Promise that's only used by tests.
updateAppMenuItem: function () {
if (this._migrationInfo) {
this.updateAppMenuItemForMigration();
return;
return Promise.resolve();
}
let profileInfoEnabled = false;
@ -241,7 +243,7 @@ let gFxAccounts = {
// state once migration is complete.
this.panelUIFooter.hidden = true;
this.panelUIFooter.removeAttribute("fxastatus");
return;
return Promise.resolve();
}
this.panelUIFooter.hidden = false;
@ -311,12 +313,18 @@ let gFxAccounts = {
}
}
// Calling getSignedInUserProfile() without a user logged in causes log
// noise that looks like an actual error...
fxAccounts.getSignedInUser().then(userData => {
return fxAccounts.getSignedInUser().then(userData => {
// userData may be null here when the user is not signed-in, but that's expected
updateWithUserData(userData);
return userData ? fxAccounts.getSignedInUserProfile() : null;
// unverified users cause us to spew log errors fetching an OAuth token
// to fetch the profile, so don't even try in that case.
if (!userData || !userData.verified || !profileInfoEnabled) {
return null; // don't even try to grab the profile.
}
return fxAccounts.getSignedInUserProfile().catch(err => {
// Not fetching the profile is sad but the FxA logs will already have noise.
return null;
});
}).then(profile => {
if (!profile) {
return;
@ -327,7 +335,7 @@ let gFxAccounts = {
// The most likely scenario is a user logged out, so reflect that.
// Bug 995134 calls for better errors so we could retry if we were
// sure this was the failure reason.
this.FxAccountsCommon.log.error("Error updating FxA profile", error);
this.FxAccountsCommon.log.error("Error updating FxA account info", error);
updateWithUserData(null);
});
},

View File

@ -29,7 +29,7 @@ let TrackingProtection = {
this.disabledTooltipText =
gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
this.enabledHistogram.add(this.enabledGlobally);
this.enabledHistogramAdd(this.enabledGlobally);
},
uninit() {
@ -55,12 +55,25 @@ let TrackingProtection = {
this.container.hidden = !this.enabled;
},
get enabledHistogram() {
return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
enabledHistogramAdd(value) {
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
},
get eventsHistogram() {
return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
eventsHistogramAdd(value) {
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
},
shieldHistogramAdd(value) {
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
},
onSecurityChange(state, isSimulated) {
@ -91,18 +104,25 @@ let TrackingProtection = {
gPrefService.savePrefFile(null);
this.showIntroPanel();
}
this.shieldHistogramAdd(2);
} else if (isAllowing) {
this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
this.icon.setAttribute("state", "loaded-tracking-content");
this.content.setAttribute("state", "loaded-tracking-content");
this.shieldHistogramAdd(1);
} else {
this.icon.removeAttribute("tooltiptext");
this.icon.removeAttribute("state");
this.content.removeAttribute("state");
// We didn't show the shield
this.shieldHistogramAdd(0);
}
// Telemetry for state change.
this.eventsHistogram.add(0);
this.eventsHistogramAdd(0);
},
disableForCurrentPage() {
@ -124,7 +144,7 @@ let TrackingProtection = {
}
// Telemetry for disable protection.
this.eventsHistogram.add(1);
this.eventsHistogramAdd(1);
// Hide the control center.
document.getElementById("identity-popup").hidePopup();
@ -147,7 +167,7 @@ let TrackingProtection = {
}
// Telemetry for enable protection.
this.eventsHistogram.add(2);
this.eventsHistogramAdd(2);
// Hide the control center.
document.getElementById("identity-popup").hidePopup();

View File

@ -6869,10 +6869,6 @@ var gIdentityHandler = {
nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT |
nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT)) {
this.showBadContentDoorhanger(state);
} else if (TrackingProtection.enabled) {
// We didn't show the shield
Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD")
.add(0);
}
},
@ -6894,19 +6890,6 @@ var gIdentityHandler = {
// default
let iconState = "bad-content-blocked-notification-icon";
// Telemetry for whether the shield was due to tracking protection or not
let histogram = Services.telemetry.getHistogramById
("TRACKING_PROTECTION_SHIELD");
if (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) {
histogram.add(1);
} else if (state &
Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) {
histogram.add(2);
} else if (gPrefService.getBoolPref("privacy.trackingprotection.enabled")) {
// Tracking protection is enabled but no tracking elements are loaded,
// the shield is due to mixed content.
histogram.add(3);
}
if (state &
(Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT)) {
@ -7576,17 +7559,30 @@ function safeModeRestart() {
* delta is the offset to the history entry that you want to load.
*/
function duplicateTabIn(aTab, where, delta) {
let newTab = SessionStore.duplicateTab(window, aTab, delta);
switch (where) {
case "window":
gBrowser.hideTab(newTab);
gBrowser.replaceTabWithWindow(newTab);
let otherWin = OpenBrowserWindow();
let delayedStartupFinished = (subject, topic) => {
if (topic == "browser-delayed-startup-finished" &&
subject == otherWin) {
Services.obs.removeObserver(delayedStartupFinished, topic);
let otherGBrowser = otherWin.gBrowser;
let otherTab = otherGBrowser.selectedTab;
SessionStore.duplicateTab(otherWin, aTab, delta);
otherGBrowser.removeTab(otherTab, { animate: false });
}
};
Services.obs.addObserver(delayedStartupFinished,
"browser-delayed-startup-finished",
false);
break;
case "tabshifted":
SessionStore.duplicateTab(window, aTab, delta);
// A background tab has been opened, nothing else to do here.
break;
case "tab":
let newTab = SessionStore.duplicateTab(window, aTab, delta);
gBrowser.selectedTab = newTab;
break;
}

View File

@ -303,6 +303,8 @@ skip-if = true # browser_drag.js is disabled, as it needs to be updated for the
[browser_focusonkeydown.js]
[browser_fullscreen-window-open.js]
skip-if = buildapp == 'mulet' || e10s || os == "linux" # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly. Linux: Intermittent failures - bug 941575.
[browser_fxaccounts.js]
support-files = fxa_profile_handler.sjs
[browser_fxa_migrate.js]
[browser_fxa_oauth.js]
[browser_fxa_web_channel.js]
@ -436,6 +438,10 @@ support-files =
benignPage.html
[browser_trackingUI_5.js]
tags = trackingprotection
support-files =
trackingPage.html
[browser_trackingUI_telemetry.js]
tags = trackingprotection
support-files =
trackingPage.html
[browser_typeAheadFind.js]

View File

@ -0,0 +1,258 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
let {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
let FxAccountsCommon = {};
Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
// instrument gFxAccounts to send observer notifications when it's done
// what it does.
(function() {
let unstubs = {}; // The original functions we stub out.
// The stub functions.
let stubs = {
updateAppMenuItem: function() {
return unstubs['updateAppMenuItem'].call(gFxAccounts).then(() => {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateAppMenuItem", null);
});
},
// Opening preferences is trickier than it should be as leaks are reported
// due to the promises it fires off at load time and there's no clear way to
// know when they are done.
// So just ensure openPreferences is called rather than whether it opens.
openPreferences: function() {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences", null);
}
};
for (let name in stubs) {
unstubs[name] = gFxAccounts[name];
gFxAccounts[name] = stubs[name];
}
// and undo our damage at the end.
registerCleanupFunction(() => {
for (let name in unstubs) {
gFxAccounts[name] = unstubs[name];
}
stubs = unstubs = null;
});
})();
// Other setup/cleanup
let newTab;
Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
TEST_ROOT + "accounts_testRemoteCommands.html");
registerCleanupFunction(() => {
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
gBrowser.removeTab(newTab);
});
add_task(function* initialize() {
// Set a new tab with something other than about:blank, so it doesn't get reused.
// We must wait for it to load or the promiseTabOpen() call in the next test
// gets confused.
newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
yield promiseTabLoaded(newTab);
});
// The elements we care about.
let panelUILabel = document.getElementById("PanelUI-fxa-label");
let panelUIStatus = document.getElementById("PanelUI-fxa-status");
let panelUIFooter = document.getElementById("PanelUI-footer-fxa");
// The tests
add_task(function* test_nouser() {
let user = yield fxAccounts.getSignedInUser();
Assert.strictEqual(user, null, "start with no user signed in");
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, null);
yield promiseUpdateDone;
// Check the world - the FxA footer area is visible as it is offering a signin.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
Assert.ok(!panelUIStatus.hasAttribute("tooltiptext"), "no tooltip when signed out");
Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
let promiseOpen = promiseTabOpen("about:accounts?entryPoint=menupanel");
panelUIStatus.click();
yield promiseOpen;
});
/*
XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
add_task(function* test_unverifiedUser() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
yield setSignedInUser(false); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened
yield signOut();
});
*/
add_task(function* test_verifiedUserEmptyProfile() {
// We see 2 updateAppMenuItem() calls - one for the signedInUser and one after
// we first fetch the profile. We want them both to fire or we aren't testing
// the state we think we are testing.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
configureProfileURL({}); // successful but empty profile.
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened;
yield signOut();
});
add_task(function* test_verifiedUserDisplayName() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
configureProfileURL({ displayName: "Test User Display Name" });
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
add_task(function* test_verifiedUserProfileFailure() {
// profile failure means only one observer fires.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 1);
configureProfileURL(null, 500);
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
// Helpers.
function isFooterVisible() {
let style = window.getComputedStyle(panelUIFooter);
return style.getPropertyValue("display") == "flex";
}
function configureProfileURL(profile, responseStatus = 200) {
let responseBody = profile ? JSON.stringify(profile) : "";
let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
"responseStatus=" + responseStatus +
"responseBody=" + responseBody +
// This is a bit cheeky - the FxA code will just append "/profile"
// to the preference value. We arrange for this to be seen by our
//.sjs as part of the query string.
"&path=";
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
}
function promiseObserver(topic, count = 1) {
return new Promise(resolve => {
let obs = (subject, topic, data) => {
if (--count == 0) {
Services.obs.removeObserver(obs, topic);
resolve(subject);
}
}
Services.obs.addObserver(obs, topic, false);
});
}
// Stolen from browser_aboutHome.js
function promiseWaitForEvent(node, type, capturing) {
return new Promise((resolve) => {
node.addEventListener(type, function listener(event) {
node.removeEventListener(type, listener, capturing);
resolve(event);
}, capturing);
});
}
let promiseTabOpen = Task.async(function*(urlBase) {
info("Waiting for tab to open...");
let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
let tab = event.target;
yield promiseTabLoadEvent(tab);
ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
"Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
let whenUnloaded = promiseTabUnloaded(tab);
gBrowser.removeTab(tab);
yield whenUnloaded;
});
function promiseTabUnloaded(tab)
{
return new Promise(resolve => {
info("Wait for tab to unload");
function handle(event) {
tab.linkedBrowser.removeEventListener("unload", handle, true);
info("Got unload event");
resolve(event);
}
tab.linkedBrowser.addEventListener("unload", handle, true, true);
});
}
// FxAccounts helpers.
function setSignedInUser(verified) {
let data = {
email: "foo@example.com",
uid: "1234@lcip.org",
assertion: "foobar",
sessionToken: "dead",
kA: "beef",
kB: "cafe",
verified: verified,
oauthTokens: {
// a token for the profile server.
profile: "key value",
}
}
return fxAccounts.setSignedInUser(data);
}
let signOut = Task.async(function* () {
// This test needs to make sure that any updates for the logout have
// completed before starting the next test, or we see the observer
// notifications get out of sync.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
// we always want a "localOnly" signout here...
yield fxAccounts.signOut(true);
yield promiseUpdateDone;
});

View File

@ -0,0 +1,145 @@
/*
* Test telemetry for Tracking Protection
*/
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const PREF = "privacy.trackingprotection.enabled";
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
/**
* Enable local telemetry recording for the duration of the tests.
*/
let oldCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
Services.prefs.setBoolPref(PREF, false);
Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear();
registerCleanupFunction(function () {
UrlClassifierTestUtils.cleanupTestTrackers();
Services.telemetry.canRecordExtended = oldCanRecord;
Services.prefs.clearUserPref(PREF);
});
function getShieldHistogram() {
return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD");
}
function getEnabledHistogram() {
return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
}
function getEventsHistogram() {
return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
}
function getShieldCounts() {
return getShieldHistogram().snapshot().counts;
}
function getEnabledCounts() {
return getEnabledHistogram().snapshot().counts;
}
function getEventCounts() {
return getEventsHistogram().snapshot().counts;
}
add_task(function* setup() {
yield UrlClassifierTestUtils.addTestTrackers();
let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
ok(TrackingProtection, "TP is attached to the browser window");
ok(!TrackingProtection.enabled, "TP is not enabled");
// Open a window with TP disabled to make sure 'enabled' is logged correctly.
let newWin = yield promiseOpenAndLoadWindow({}, true);
yield promiseWindowClosed(newWin);
is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
is(getEnabledCounts()[1], 0, "TP was not enabled on start up");
// Enable TP so the next browser to open will log 'enabled'
Services.prefs.setBoolPref(PREF, true);
});
add_task(function* testNewWindow() {
let newWin = yield promiseOpenAndLoadWindow({}, true);
let tab = newWin.gBrowser.selectedTab = newWin.gBrowser.addTab();
let TrackingProtection = newWin.TrackingProtection;
ok(TrackingProtection, "TP is attached to the browser window");
is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
is(getEnabledCounts()[1], 1, "TP was enabled once on start up");
// Reset these to make counting easier
getEventsHistogram().clear();
getShieldHistogram().clear();
yield promiseTabLoadEvent(tab, BENIGN_PAGE);
is(getEventCounts()[0], 1, "Total page loads");
is(getEventCounts()[1], 0, "Disable actions");
is(getEventCounts()[2], 0, "Enable actions");
is(getShieldCounts()[0], 1, "Page loads without tracking");
yield promiseTabLoadEvent(tab, TRACKING_PAGE);
// Note that right now the events and shield histogram is not measuring what
// you might think. Since onSecurityChange fires twice for a tracking page,
// the total page loads count is double counting, and the shield count
// (which is meant to measure times when the shield wasn't shown) fires even
// when tracking elements exist on the page.
todo_is(getEventCounts()[0], 2, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
is(getEventCounts()[1], 0, "Disable actions");
is(getEventCounts()[2], 0, "Enable actions");
todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
info("Disable TP for the page (which reloads the page)");
let tabReloadPromise = promiseTabLoadEvent(tab);
newWin.document.querySelector("#tracking-action-unblock").doCommand();
yield tabReloadPromise;
todo_is(getEventCounts()[0], 3, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
is(getEventCounts()[1], 1, "Disable actions");
is(getEventCounts()[2], 0, "Enable actions");
todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
info("Re-enable TP for the page (which reloads the page)");
tabReloadPromise = promiseTabLoadEvent(tab);
newWin.document.querySelector("#tracking-action-block").doCommand();
yield tabReloadPromise;
todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
is(getEventCounts()[1], 1, "Disable actions");
is(getEventCounts()[2], 1, "Enable actions");
todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
yield promiseWindowClosed(newWin);
// Reset these to make counting easier for the next test
getEventsHistogram().clear();
getShieldHistogram().clear();
getEnabledHistogram().clear();
});
add_task(function* testPrivateBrowsing() {
let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
let tab = privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab();
let TrackingProtection = privateWin.TrackingProtection;
ok(TrackingProtection, "TP is attached to the browser window");
// Do a bunch of actions and make sure that no telemetry data is gathered
yield promiseTabLoadEvent(tab, BENIGN_PAGE);
yield promiseTabLoadEvent(tab, TRACKING_PAGE);
let tabReloadPromise = promiseTabLoadEvent(tab);
privateWin.document.querySelector("#tracking-action-unblock").doCommand();
yield tabReloadPromise;
tabReloadPromise = promiseTabLoadEvent(tab);
privateWin.document.querySelector("#tracking-action-block").doCommand();
yield tabReloadPromise;
// Sum up all the counts to make sure that nothing got logged
is(getEnabledCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
is(getEventCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
is(getShieldCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
yield promiseWindowClosed(privateWin);
});

View File

@ -1,6 +1,7 @@
<html>
<head>
<title>Dummy test page</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
</head>
<body>
<p>Dummy test page</p>

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// This is basically an echo server!
// We just grab responseStatus and responseBody query params!
function reallyHandleRequest(request, response) {
var query = "?" + request.queryString;
var responseStatus = 200;
var match = /responseStatus=([^&]*)/.exec(query);
if (match) {
responseStatus = parseInt(match[1]);
}
var responseBody = "";
match = /responseBody=([^&]*)/.exec(query);
if (match) {
responseBody = decodeURIComponent(match[1]);
}
response.setStatusLine("1.0", responseStatus, "OK");
response.write(responseBody);
}
function handleRequest(request, response)
{
try {
reallyHandleRequest(request, response);
} catch (e) {
response.setStatusLine("1.0", 500, "NotOK");
response.write("Error handling request: " + e);
}
}

View File

@ -12,6 +12,7 @@
"browser": true,
"mocha": true
},
"extends": "eslint:recommended",
"globals": {
"_": false,
"$": false,
@ -35,31 +36,75 @@
// problems they find, one at a time.
// Eslint built-in rules are documented at <http://eslint.org/docs/rules/>
"camelcase": 0, // TODO: Remove (use default)
"callback-return": 0, // TBD
"camelcase": 0, // TODO: set to 2
"comma-spacing": 2,
"computed-property-spacing": [2, "never"],
"consistent-return": 0, // TODO: Remove (use default)
"consistent-return": 0, // TODO: set to 2
"curly": [2, "all"],
dot-location: 0, // [2, property],
"eol-last": 2,
"eqeqeq": 0, // TBD. Might need to be separate for content & chrome
"global-strict": 0, // Leave as zero (this will be unsupported in eslint 1.0.0)
"key-spacing": [2, {"beforeColon": false, "afterColon": true }],
"linebreak-style": [2, "unix"],
"new-cap": 0, // TODO: Remove (use default)
"no-catch-shadow": 0, // TODO: Remove (use default)
"new-cap": 0, // TODO: set to 2
"new-parens": 2,
"no-alert": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0, // TODO: set to 2
"no-class-assign": 2,
"no-const-assign": 2,
"no-console": 0, // Leave as 0. We use console logging in content code.
"no-empty": 0, // TODO: Remove (use default)
"no-empty": 0, // TODO: set to 2
"no-empty-label": 2,
"no-eval": 2,
"no-extend-native": 2, // XXX
"no-extra-bind": 0, // Leave as 0
"no-extra-parens": 0, // TODO: (bug?) [2, "functions"],
"no-implied-eval": 2,
"no-invalid-this": 0, // TBD
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 0, // TBD.
"no-new": 0, // TODO: Remove (use default)
"no-return-assign": 0, // TODO: Remove (use default)
"no-multi-str": 2,
"no-native-reassign": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-undef-init": 2,
"no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables.
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unused-expressions": 0, // TODO: Remove (use default)
"no-unused-vars": 0, // TODO: Remove (use default)
"no-use-before-define": 0, // TODO: Remove (use default)
"no-unused-expressions": 0, // TODO: Set to 2
"no-unused-vars": 0, // TODO: Set to 2
"no-use-before-define": 0, // TODO: Set to 2
"no-useless-call": 2,
"no-with": 2,
"object-curly-spacing": 0, // [2, "always"],
"quotes": [2, "double", "avoid-escape"],
"semi": 2,
"semi-spacing": [2, {"before": false, "after": true}],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, {"words": true, "nonwords": false}],
"spaced-comment": [2, "always"],
"strict": [2, "function"],
"yoda": [2, "never"],
// eslint-plugin-react rules. These are documented at
// <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
"react/jsx-quotes": [2, "double", "avoid-escape"],
@ -67,6 +112,7 @@
"react/jsx-sort-props": 2,
"react/jsx-sort-prop-types": 2,
"react/jsx-uses-vars": 2,
"react/jsx-no-duplicate-props": 2,
// Need to fix the couple of instances which don't
// currently pass this rule.
"react/no-did-mount-set-state": 0,

View File

@ -56,9 +56,12 @@
"Assert": false,
},
"rules": {
"arrow-parens": 0, // TBD
"arrow-spacing": 2,
"generator-star-spacing": [2, "after"],
// We should fix the errors and enable this (set to 2)
"no-var": 0,
"require-yield": 0, // TODO: Set to 2.
"strict": [2, "global"]
}
}

View File

@ -283,3 +283,31 @@ html[dir="rtl"] .contacts-gravatar-promo > .button-close {
right: auto;
left: 8px;
}
.contact-controls {
padding: 0 16px;
}
.contact-controls > .button {
padding: .5em;
border: none;
border-radius: 5px;
}
.button.primary {
background: #00A9DC;
color: #fff;
}
.button.secondary {
background: #EBEBEB;
color: #4D4D4D;
}
.contact-controls > .primary {
flex: 5;
}
.contact-controls > .secondary {
flex: 3;
}

View File

@ -435,10 +435,7 @@ body {
border-radius: 2px;
min-height: 26px;
font-size: 1.2rem;
}
.button > .button-caption {
vertical-align: middle;
line-height: 1.2rem;
}
.button:hover {
@ -641,7 +638,6 @@ html[dir="rtl"] .generate-url-spinner {
/* Undo the start border + padding so that unhovered dnd-status is aligned
as if there was no additional spacing. */
-moz-margin-start: calc(-1px + -4px);
font-size: .9em;
cursor: pointer;
border-radius: 3px;
}
@ -651,8 +647,33 @@ html[dir="rtl"] .generate-url-spinner {
background-color: #f1f1f1;
}
/* Status badges -- Available/Unavailable */
.status-available:before,
.status-unavailable:before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
vertical-align: bottom;
background-repeat: no-repeat;
background-size: cover;
-moz-margin-end: .2rem;
margin-bottom: -2px;
}
html[dir="rtl"] .dropdown-menu-item.status-available:before,
html[dir="rtl"] .dropdown-menu-item.status-unavailable:before {
margin-right: -3px;
}
.status-available:before {
background-image: url("../shared/img/icons-16x16.svg#status-available");
}
.status-unavailable:before {
background-image: url("../shared/img/icons-16x16.svg#status-unavailable");
}
/* Status badges -- Available/Unavailable */
.status {
display: inline-block;
width: 8px;
@ -661,14 +682,6 @@ html[dir="rtl"] .generate-url-spinner {
border-radius: 50%;
}
.status-available {
background-color: #6cb23e;
}
.status-dnd {
border: 1px solid #888;
}
/* Sign in/up link */
.signin-link {
@ -677,42 +690,36 @@ html[dir="rtl"] .generate-url-spinner {
text-align: right;
}
.signin-link a {
font-size: .9em;
.signin-link > a {
font-weight: 500;
text-decoration: none;
color: #888;
}
.footer-signin-separator {
border-right: 1px solid #aaa;
height: 16px;
margin: 0 1em;
color: #00A9DC;
}
/* Settings (gear) menu */
.button-settings {
display: inline-block;
overflow: hidden;
width: 10px;
height: 10px;
margin: 0;
padding: 0;
border: none;
background-color: #a5a;
color: #fff;
text-align: center;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
font-size: .9em;
cursor: pointer;
background: transparent url(../shared/img/svg/glyph-settings-16x16.svg) no-repeat center center;
background-size: contain;
width: 12px;
height: 12px;
vertical-align: middle;
background: transparent url("../shared/img/icons-10x10.svg#settings-cog");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
-moz-margin-start: .5em;
}
.footer .button-settings {
opacity: .6; /* used to "grey" the icon a little */
.user-details .dropdown-menu {
bottom: 1.3rem; /* Just above the text. */
left: -5px; /* Compensate for button padding. */
}
html[dir="rtl"] .user-details .dropdown-menu {
right: -5px;
}
.settings-menu .dropdown-menu {
@ -721,7 +728,7 @@ html[dir="rtl"] .generate-url-spinner {
set by .dropdown-menu */
top: auto;
left: auto;
bottom: -8px;
bottom: 1.1em;
right: 14px;
}
@ -735,34 +742,32 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
.settings-menu .icon {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 12px;
height: 12px;
-moz-margin-end: 1em;
margin-top: 2px;
}
.settings-menu .icon-settings {
background: transparent url(../shared/img/svg/glyph-settings-16x16.svg) no-repeat center center;
}
.settings-menu .icon-tour {
background: transparent url("../shared/img/icons-16x16.svg#tour") no-repeat center center;
background-image: url("../shared/img/icons-16x16.svg#tour");
}
.settings-menu .icon-account {
background: transparent url(../shared/img/svg/glyph-account-16x16.svg) no-repeat center center;
background-image: url(../shared/img/svg/glyph-account-16x16.svg);
}
.settings-menu .icon-signin {
background: transparent url(../shared/img/svg/glyph-signin-16x16.svg) no-repeat center center;
background-image: url(../shared/img/svg/glyph-signin-16x16.svg);
}
.settings-menu .icon-signout {
background: transparent url(../shared/img/svg/glyph-signout-16x16.svg) no-repeat center center;
background-image: url(../shared/img/svg/glyph-signout-16x16.svg);
}
.settings-menu .icon-help {
background: transparent url(../shared/img/svg/glyph-help-16x16.svg) no-repeat center center;
background-image: url(../shared/img/svg/glyph-help-16x16.svg);
}
/* Footer */
@ -774,11 +779,10 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
justify-content: space-between;
align-content: stretch;
align-items: center;
font-size: 1em;
border-top: 1px solid #D1D1D1;
background-color: #eaeaea;
color: #7f7f7f;
padding: .5rem 1rem;
font-size: 1rem;
background-color: #fff;
color: #666666;
padding: .5rem 15px;
}
.footer .signin-details {

View File

@ -590,19 +590,6 @@ loop.contacts = (function(_, mozL10n) {
return (
React.createElement("div", null,
React.createElement("div", {className: "content-area"},
React.createElement(ButtonGroup, null,
React.createElement(Button, {caption: this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button2"),
disabled: this.state.importBusy,
onClick: this.handleImportButtonClick},
React.createElement("div", {className: cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})})
),
React.createElement(Button, {caption: mozL10n.get("new_contact_button"),
onClick: this.handleAddContactButtonClick})
),
showFilter ?
React.createElement("input", {className: "contact-filter",
placeholder: mozL10n.get("contacts_search_placesholder"),
@ -620,6 +607,21 @@ loop.contacts = (function(_, mozL10n) {
shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null
),
React.createElement(ButtonGroup, {additionalClass: "contact-controls"},
React.createElement(Button, {additionalClass: "secondary",
caption: this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3"),
disabled: this.state.importBusy,
onClick: this.handleImportButtonClick},
React.createElement("div", {className: cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})})
),
React.createElement(Button, {additionalClass: "primary",
caption: mozL10n.get("new_contact_button"),
onClick: this.handleAddContactButtonClick})
)
)
);

View File

@ -590,19 +590,6 @@ loop.contacts = (function(_, mozL10n) {
return (
<div>
<div className="content-area">
<ButtonGroup>
<Button caption={this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button2")}
disabled={this.state.importBusy}
onClick={this.handleImportButtonClick}>
<div className={cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})} />
</Button>
<Button caption={mozL10n.get("new_contact_button")}
onClick={this.handleAddContactButtonClick} />
</ButtonGroup>
{showFilter ?
<input className="contact-filter"
placeholder={mozL10n.get("contacts_search_placesholder")}
@ -621,6 +608,21 @@ loop.contacts = (function(_, mozL10n) {
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null}
</ul>
<ButtonGroup additionalClass="contact-controls">
<Button additionalClass="secondary"
caption={this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3")}
disabled={this.state.importBusy}
onClick={this.handleImportButtonClick} >
<div className={cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})} />
</Button>
<Button additionalClass="primary"
caption={mozL10n.get("new_contact_button")}
onClick={this.handleAddContactButtonClick} />
</ButtonGroup>
</div>
);
}

View File

@ -143,36 +143,35 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
// XXX https://github.com/facebook/react/issues/310 for === htmlFor
var cx = React.addons.classSet;
var availabilityStatus = cx({
"status": true,
"status-dnd": this.state.doNotDisturb,
"status-available": !this.state.doNotDisturb
});
var availabilityDropdown = cx({
"dropdown-menu": true,
"hide": !this.state.showMenu
});
var statusIcon = cx({
"status-unavailable": this.state.doNotDisturb,
"status-available": !this.state.doNotDisturb
});
var availabilityText = this.state.doNotDisturb ?
mozL10n.get("display_name_dnd_status") :
mozL10n.get("display_name_available_status");
mozL10n.get("display_name_dnd_status") :
mozL10n.get("display_name_available_status");
return (
React.createElement("div", {className: "dropdown"},
React.createElement("p", {className: "dnd-status", onClick: this.toggleDropdownMenu, ref: "menu-button"},
React.createElement("span", null, availabilityText),
React.createElement("i", {className: availabilityStatus})
React.createElement("p", {className: "dnd-status"},
React.createElement("span", {className: statusIcon,
onClick: this.toggleDropdownMenu,
ref: "menu-button"},
availabilityText
)
),
React.createElement("ul", {className: availabilityDropdown},
React.createElement("li", {className: "dropdown-menu-item dnd-make-available",
React.createElement("li", {className: "dropdown-menu-item status-available",
onClick: this.changeAvailability("available")},
React.createElement("i", {className: "status status-available"}),
React.createElement("span", null, mozL10n.get("display_name_available_status"))
),
React.createElement("li", {className: "dropdown-menu-item dnd-make-unavailable",
React.createElement("li", {className: "dropdown-menu-item status-unavailable",
onClick: this.changeAvailability("do-not-disturb")},
React.createElement("i", {className: "status status-dnd"}),
React.createElement("span", null, mozL10n.get("display_name_dnd_status"))
)
)
@ -409,7 +408,7 @@ loop.panel = (function(_, mozL10n) {
return (
React.createElement("div", {className: "settings-menu dropdown"},
React.createElement("a", {className: "button-settings",
React.createElement("button", {className: "button-settings",
onClick: this.toggleDropdownMenu,
ref: "menu-button",
title: mozL10n.get("settings_menu_button_tooltip")}),
@ -443,21 +442,35 @@ loop.panel = (function(_, mozL10n) {
/**
* FxA sign in/up link component.
*/
var AuthLink = React.createClass({displayName: "AuthLink",
var AccountLink = React.createClass({displayName: "AccountLink",
mixins: [sharedMixins.WindowCloseMixin],
handleSignUpLinkClick: function() {
propTypes: {
fxAEnabled: React.PropTypes.bool.isRequired,
userProfile: userProfileValidator
},
handleSignInLinkClick: function() {
navigator.mozLoop.logInToFxA();
this.closeWindow();
},
render: function() {
if (!navigator.mozLoop.fxAEnabled || navigator.mozLoop.userProfile) {
if (!this.props.fxAEnabled) {
return null;
}
if (this.props.userProfile && this.props.userProfile.email) {
return (
React.createElement("div", {className: "user-identity"},
loop.shared.utils.truncate(this.props.userProfile.email, 24)
)
);
}
return (
React.createElement("p", {className: "signin-link"},
React.createElement("a", {href: "#", onClick: this.handleSignUpLinkClick},
React.createElement("a", {href: "#", onClick: this.handleSignInLinkClick},
mozL10n.get("panel_footer_signin_or_signup_link")
)
)
@ -465,23 +478,6 @@ loop.panel = (function(_, mozL10n) {
}
});
/**
* FxA user identity (guest/authenticated) component.
*/
var UserIdentity = React.createClass({displayName: "UserIdentity",
propTypes: {
displayName: React.PropTypes.string.isRequired
},
render: function() {
return (
React.createElement("p", {className: "user-identity"},
this.props.displayName
)
);
}
});
var RoomEntryContextItem = React.createClass({displayName: "RoomEntryContextItem",
mixins: [loop.shared.mixins.WindowCloseMixin],
@ -612,6 +608,18 @@ loop.panel = (function(_, mozL10n) {
}
});
/*
* User profile prop can be either an object or null as per mozLoopAPI
* and there is no way to express this with React 0.12.2
*/
function userProfileValidator(props, propName, componentName) {
if (Object.prototype.toString.call(props[propName]) !== "[object Object]" &&
!_.isNull(props[propName])) {
return new Error("Required prop `" + propName +
"` was not correctly specified in `" + componentName + "`.");
}
}
/**
* Room list.
*/
@ -622,7 +630,8 @@ loop.panel = (function(_, mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired,
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
userDisplayName: React.PropTypes.string.isRequired // for room creation
// Used for room creation, associated with room owner.
userProfile: userProfileValidator
},
getInitialState: function() {
@ -663,6 +672,11 @@ loop.panel = (function(_, mozL10n) {
return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
},
_getUserDisplayName: function() {
return this.props.userProfile && this.props.userProfile.email ||
mozL10n.get("display_name_guest");
},
render: function() {
if (this.state.error) {
// XXX Better end user reporting of errors.
@ -687,7 +701,7 @@ loop.panel = (function(_, mozL10n) {
mozLoop: this.props.mozLoop,
pendingOperation: this.state.pendingCreation ||
this.state.pendingInitialRetrieval,
userDisplayName: this.props.userDisplayName})
userDisplayName: this._getUserDisplayName()})
)
);
}
@ -815,15 +829,13 @@ loop.panel = (function(_, mozL10n) {
React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
selectedTab: React.PropTypes.string,
// Used only for unit tests.
showTabButtons: React.PropTypes.bool,
// Mostly used for UI components showcase and unit tests
userProfile: React.PropTypes.object
showTabButtons: React.PropTypes.bool
},
getInitialState: function() {
return {
hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
userProfile: this.props.mozLoop.userProfile,
gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
};
},
@ -918,11 +930,6 @@ loop.panel = (function(_, mozL10n) {
window.removeEventListener("UIAction", this._UIActionHandler);
},
_getUserDisplayName: function() {
return this.state.userProfile && this.state.userProfile.email ||
mozL10n.get("display_name_guest");
},
render: function() {
var NotificationListView = sharedViews.NotificationListView;
@ -962,7 +969,7 @@ loop.panel = (function(_, mozL10n) {
React.createElement(RoomList, {dispatcher: this.props.dispatcher,
mozLoop: this.props.mozLoop,
store: this.props.roomStore,
userDisplayName: this._getUserDisplayName()}),
userProfile: this.state.userProfile}),
React.createElement(ToSView, null)
),
React.createElement(Tab, {name: "contacts"},
@ -992,12 +999,11 @@ loop.panel = (function(_, mozL10n) {
),
React.createElement("div", {className: "footer"},
React.createElement("div", {className: "user-details"},
React.createElement(UserIdentity, {displayName: this._getUserDisplayName()}),
React.createElement(AvailabilityDropdown, null)
),
React.createElement("div", {className: "signin-details"},
React.createElement(AuthLink, null),
React.createElement("div", {className: "footer-signin-separator"}),
React.createElement(AccountLink, {fxAEnabled: this.props.mozLoop.fxAEnabled,
userProfile: this.state.userProfile}),
React.createElement(SettingsDropdown, {mozLoop: this.props.mozLoop})
)
)
@ -1038,18 +1044,17 @@ loop.panel = (function(_, mozL10n) {
}
return {
init: init,
AuthLink: AuthLink,
AccountLink: AccountLink,
AvailabilityDropdown: AvailabilityDropdown,
GettingStartedView: GettingStartedView,
init: init,
NewRoomView: NewRoomView,
PanelView: PanelView,
RoomEntry: RoomEntry,
RoomList: RoomList,
SettingsDropdown: SettingsDropdown,
SignInRequestView: SignInRequestView,
ToSView: ToSView,
UserIdentity: UserIdentity
ToSView: ToSView
};
})(_, document.mozL10n);

View File

@ -143,36 +143,35 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
// XXX https://github.com/facebook/react/issues/310 for === htmlFor
var cx = React.addons.classSet;
var availabilityStatus = cx({
"status": true,
"status-dnd": this.state.doNotDisturb,
"status-available": !this.state.doNotDisturb
});
var availabilityDropdown = cx({
"dropdown-menu": true,
"hide": !this.state.showMenu
});
var statusIcon = cx({
"status-unavailable": this.state.doNotDisturb,
"status-available": !this.state.doNotDisturb
});
var availabilityText = this.state.doNotDisturb ?
mozL10n.get("display_name_dnd_status") :
mozL10n.get("display_name_available_status");
mozL10n.get("display_name_dnd_status") :
mozL10n.get("display_name_available_status");
return (
<div className="dropdown">
<p className="dnd-status" onClick={this.toggleDropdownMenu} ref="menu-button">
<span>{availabilityText}</span>
<i className={availabilityStatus}></i>
<p className="dnd-status">
<span className={statusIcon}
onClick={this.toggleDropdownMenu}
ref="menu-button">
{availabilityText}
</span>
</p>
<ul className={availabilityDropdown}>
<li className="dropdown-menu-item dnd-make-available"
<li className="dropdown-menu-item status-available"
onClick={this.changeAvailability("available")}>
<i className="status status-available"></i>
<span>{mozL10n.get("display_name_available_status")}</span>
</li>
<li className="dropdown-menu-item dnd-make-unavailable"
<li className="dropdown-menu-item status-unavailable"
onClick={this.changeAvailability("do-not-disturb")}>
<i className="status status-dnd"></i>
<span>{mozL10n.get("display_name_dnd_status")}</span>
</li>
</ul>
@ -409,7 +408,7 @@ loop.panel = (function(_, mozL10n) {
return (
<div className="settings-menu dropdown">
<a className="button-settings"
<button className="button-settings"
onClick={this.toggleDropdownMenu}
ref="menu-button"
title={mozL10n.get("settings_menu_button_tooltip")} />
@ -443,21 +442,35 @@ loop.panel = (function(_, mozL10n) {
/**
* FxA sign in/up link component.
*/
var AuthLink = React.createClass({
var AccountLink = React.createClass({
mixins: [sharedMixins.WindowCloseMixin],
handleSignUpLinkClick: function() {
propTypes: {
fxAEnabled: React.PropTypes.bool.isRequired,
userProfile: userProfileValidator
},
handleSignInLinkClick: function() {
navigator.mozLoop.logInToFxA();
this.closeWindow();
},
render: function() {
if (!navigator.mozLoop.fxAEnabled || navigator.mozLoop.userProfile) {
if (!this.props.fxAEnabled) {
return null;
}
if (this.props.userProfile && this.props.userProfile.email) {
return (
<div className="user-identity">
{loop.shared.utils.truncate(this.props.userProfile.email, 24)}
</div>
);
}
return (
<p className="signin-link">
<a href="#" onClick={this.handleSignUpLinkClick}>
<a href="#" onClick={this.handleSignInLinkClick}>
{mozL10n.get("panel_footer_signin_or_signup_link")}
</a>
</p>
@ -465,23 +478,6 @@ loop.panel = (function(_, mozL10n) {
}
});
/**
* FxA user identity (guest/authenticated) component.
*/
var UserIdentity = React.createClass({
propTypes: {
displayName: React.PropTypes.string.isRequired
},
render: function() {
return (
<p className="user-identity">
{this.props.displayName}
</p>
);
}
});
var RoomEntryContextItem = React.createClass({
mixins: [loop.shared.mixins.WindowCloseMixin],
@ -612,6 +608,18 @@ loop.panel = (function(_, mozL10n) {
}
});
/*
* User profile prop can be either an object or null as per mozLoopAPI
* and there is no way to express this with React 0.12.2
*/
function userProfileValidator(props, propName, componentName) {
if (Object.prototype.toString.call(props[propName]) !== "[object Object]" &&
!_.isNull(props[propName])) {
return new Error("Required prop `" + propName +
"` was not correctly specified in `" + componentName + "`.");
}
}
/**
* Room list.
*/
@ -622,7 +630,8 @@ loop.panel = (function(_, mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
mozLoop: React.PropTypes.object.isRequired,
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
userDisplayName: React.PropTypes.string.isRequired // for room creation
// Used for room creation, associated with room owner.
userProfile: userProfileValidator
},
getInitialState: function() {
@ -663,6 +672,11 @@ loop.panel = (function(_, mozL10n) {
return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
},
_getUserDisplayName: function() {
return this.props.userProfile && this.props.userProfile.email ||
mozL10n.get("display_name_guest");
},
render: function() {
if (this.state.error) {
// XXX Better end user reporting of errors.
@ -687,7 +701,7 @@ loop.panel = (function(_, mozL10n) {
mozLoop={this.props.mozLoop}
pendingOperation={this.state.pendingCreation ||
this.state.pendingInitialRetrieval}
userDisplayName={this.props.userDisplayName} />
userDisplayName={this._getUserDisplayName()} />
</div>
);
}
@ -815,15 +829,13 @@ loop.panel = (function(_, mozL10n) {
React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
selectedTab: React.PropTypes.string,
// Used only for unit tests.
showTabButtons: React.PropTypes.bool,
// Mostly used for UI components showcase and unit tests
userProfile: React.PropTypes.object
showTabButtons: React.PropTypes.bool
},
getInitialState: function() {
return {
hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
userProfile: this.props.mozLoop.userProfile,
gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
};
},
@ -918,11 +930,6 @@ loop.panel = (function(_, mozL10n) {
window.removeEventListener("UIAction", this._UIActionHandler);
},
_getUserDisplayName: function() {
return this.state.userProfile && this.state.userProfile.email ||
mozL10n.get("display_name_guest");
},
render: function() {
var NotificationListView = sharedViews.NotificationListView;
@ -962,7 +969,7 @@ loop.panel = (function(_, mozL10n) {
<RoomList dispatcher={this.props.dispatcher}
mozLoop={this.props.mozLoop}
store={this.props.roomStore}
userDisplayName={this._getUserDisplayName()} />
userProfile={this.state.userProfile} />
<ToSView />
</Tab>
<Tab name="contacts">
@ -992,12 +999,11 @@ loop.panel = (function(_, mozL10n) {
</TabView>
<div className="footer">
<div className="user-details">
<UserIdentity displayName={this._getUserDisplayName()} />
<AvailabilityDropdown />
</div>
<div className="signin-details">
<AuthLink />
<div className="footer-signin-separator" />
<AccountLink fxAEnabled={this.props.mozLoop.fxAEnabled}
userProfile={this.state.userProfile}/>
<SettingsDropdown mozLoop={this.props.mozLoop}/>
</div>
</div>
@ -1038,18 +1044,17 @@ loop.panel = (function(_, mozL10n) {
}
return {
init: init,
AuthLink: AuthLink,
AccountLink: AccountLink,
AvailabilityDropdown: AvailabilityDropdown,
GettingStartedView: GettingStartedView,
init: init,
NewRoomView: NewRoomView,
PanelView: PanelView,
RoomEntry: RoomEntry,
RoomList: RoomList,
SettingsDropdown: SettingsDropdown,
SignInRequestView: SignInRequestView,
ToSView: ToSView,
UserIdentity: UserIdentity
ToSView: ToSView
};
})(_, document.mozL10n);

View File

@ -460,23 +460,6 @@ loop.roomViews = (function(mozL10n) {
this.props.roomData.roomContextUrls[0];
},
/**
* Truncate a string if it exceeds the length as defined in `maxLen`, which
* is defined as '72' characters by default. If the string needs trimming,
* it'll be suffixed with the unicode ellipsis char, \u2026.
*
* @param {String} str The string to truncate, if needed.
* @param {Number} maxLen Maximum number of characters that the string is
* allowed to contain. Optional, defaults to 72.
* @return {String} Truncated version of `str`.
*/
_truncate: function(str, maxLen) {
if (!maxLen) {
maxLen = 72;
}
return (str.length > maxLen) ? str.substr(0, maxLen) + "…" : str;
},
render: function() {
if (!this.state.show) {
return null;

View File

@ -460,23 +460,6 @@ loop.roomViews = (function(mozL10n) {
this.props.roomData.roomContextUrls[0];
},
/**
* Truncate a string if it exceeds the length as defined in `maxLen`, which
* is defined as '72' characters by default. If the string needs trimming,
* it'll be suffixed with the unicode ellipsis char, \u2026.
*
* @param {String} str The string to truncate, if needed.
* @param {Number} maxLen Maximum number of characters that the string is
* allowed to contain. Optional, defaults to 72.
* @return {String} Truncated version of `str`.
*/
_truncate: function(str, maxLen) {
if (!maxLen) {
maxLen = 72;
}
return (str.length > maxLen) ? str.substr(0, maxLen) + "…" : str;
},
render: function() {
if (!this.state.show) {
return null;

View File

@ -422,7 +422,6 @@ p {
background-color: #fdfdfd;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
list-style: none;
padding: 5px;
border-radius: 2px;
}
@ -432,19 +431,17 @@ html[dir="rtl"] .dropdown-menu {
}
.dropdown-menu-item {
width: 100%;
text-align: start;
margin: .3em 0;
padding: .2em .5em;
padding: .5em 15px;
cursor: pointer;
border: 1px solid transparent;
border-radius: 2px;
font-size: 1em;
white-space: nowrap;
}
.dropdown-menu-item:hover {
border: 1px solid #ccc;
background-color: #eee;
background-color: #dbf7ff;
}
.dropdown-menu-item > .icon {

View File

@ -1072,6 +1072,7 @@ html[dir="rtl"] .room-context-btn-close {
}
.media-wrapper > .focus-stream {
display: flex;
/* We want this to be the width, minus 200px which is for the right-side text
chat and video displays. */
width: calc(100% - 200px);
@ -1347,21 +1348,10 @@ html[dir="rtl"] .room-context-btn-close {
}
.standalone .room-conversation-wrapper .room-inner-info-area {
position: absolute;
/* `top` is chosen to vertically position the area near the center
of the media element. */
top: calc(50% - 140px);
left: 25%;
z-index: 1000;
/* `width` here is specified by the design spec. */
width: 250px;
color: #fff;
box-sizing: content-box;
}
html[dir="rtl"] .standalone .room-conversation-wrapper .room-inner-info-area {
right: 25%;
left: auto;
margin: auto;
/* 290px is the width of the widest info child, i.e., a tile */
width: 290px;
}
.standalone .prompt-media-message {

View File

@ -29,6 +29,7 @@
<polygon id="expand-shape" points="10,0 4.838,0 6.506,1.669 0,8.175 1.825,10 8.331,3.494 10,5.162"/>
<path id="edit-shape" d="M5.493,1.762l2.745,2.745L2.745,10H0V7.255L5.493,1.762z M2.397,9.155l0.601-0.601L1.446,7.002L0.845,7.603 V8.31H1.69v0.845H2.397z M5.849,3.028c0-0.096-0.048-0.144-0.146-0.144c-0.044,0-0.081,0.015-0.112,0.046L2.014,6.508 C1.983,6.538,1.968,6.577,1.968,6.619c0,0.098,0.048,0.146,0.144,0.146c0.044,0,0.081-0.015,0.112-0.046l3.579-3.577 C5.834,3.111,5.849,3.073,5.849,3.028z M10,2.395c0,0.233-0.081,0.431-0.245,0.595L8.66,4.085L5.915,1.34L7.01,0.25 C7.168,0.083,7.366,0,7.605,0c0.233,0,0.433,0.083,0.601,0.25l1.55,1.544C9.919,1.966,10,2.166,10,2.395z"/>
<rect id="minimize-shape" y="3.6" width="10" height="2.8"/>
<path id="cog-shape" d="M122.285722,122.071424 C122.285722,122.936938 121.579806,123.642854 120.714291,123.642854 C119.848777,123.642854 119.142861,122.936938 119.142861,122.071424 C119.142861,121.205909 119.848777,120.499993 120.714291,120.499993 C121.579806,120.499993 122.285722,121.205909 122.285722,122.071424 L122.285722,122.071424 Z M125.428583,121.402338 C125.428583,121.297985 125.354922,121.199771 125.250569,121.181356 L124.127242,121.009481 C124.065858,120.806913 123.97992,120.604346 123.875567,120.407917 C124.084273,120.119413 124.311394,119.849323 124.520099,119.566957 C124.550791,119.523988 124.569207,119.481019 124.569207,119.425773 C124.569207,119.376666 124.55693,119.327559 124.526238,119.290729 C124.268425,118.928563 123.838737,118.547982 123.513402,118.247201 C123.470433,118.21037 123.415187,118.185817 123.359942,118.185817 C123.304696,118.185817 123.249451,118.204232 123.21262,118.241062 L122.340967,118.897871 C122.162954,118.805795 121.978802,118.732134 121.788511,118.67075 L121.616636,117.541285 C121.604359,117.436932 121.506145,117.357133 121.395654,117.357133 L120.032929,117.357133 C119.922438,117.357133 119.8365,117.430793 119.811947,117.529008 C119.713732,117.897312 119.676902,118.296308 119.633933,118.67075 C119.443642,118.732134 119.253352,118.811933 119.075338,118.904009 L118.228239,118.247201 C118.179132,118.21037 118.123886,118.185817 118.068641,118.185817 C117.859935,118.185817 117.031251,119.082023 116.88393,119.28459 C116.853238,119.327559 116.828684,119.370528 116.828684,119.425773 C116.828684,119.481019 116.853238,119.530126 116.890068,119.573095 C117.117189,119.849323 117.338171,120.125551 117.546877,120.420194 C117.448662,120.604346 117.368863,120.788498 117.307479,120.984927 L116.165737,121.156802 C116.073661,121.175217 116,121.285709 116,121.377785 L116,122.74051 C116,122.844862 116.073661,122.943077 116.178014,122.961492 L117.301341,123.127229 C117.362725,123.335934 117.448662,123.538502 117.553015,123.73493 C117.34431,124.023435 117.117189,124.293525 116.908483,124.575891 C116.877791,124.61886 116.859376,124.661829 116.859376,124.717074 C116.859376,124.766182 116.871653,124.815289 116.902345,124.858258 C117.160158,125.214285 117.589846,125.594866 117.915181,125.889509 C117.95815,125.932478 118.013395,125.957031 118.068641,125.957031 C118.123886,125.957031 118.179132,125.938616 118.222101,125.901786 L119.087615,125.244977 C119.265629,125.337053 119.449781,125.410714 119.640071,125.472098 L119.811947,126.601563 C119.824223,126.705916 119.922438,126.785715 120.032929,126.785715 L121.395654,126.785715 C121.506145,126.785715 121.592083,126.712054 121.616636,126.61384 C121.714851,126.245536 121.751681,125.84654 121.79465,125.472098 C121.98494,125.410714 122.175231,125.330914 122.353244,125.238838 L123.200343,125.901786 C123.249451,125.932478 123.304696,125.957031 123.359942,125.957031 C123.568647,125.957031 124.397331,125.054686 124.544653,124.858258 C124.581483,124.815289 124.599899,124.77232 124.599899,124.717074 C124.599899,124.661829 124.575345,124.606583 124.538515,124.563614 C124.311394,124.287386 124.090411,124.017297 123.881706,123.716515 C123.97992,123.538502 124.053581,123.35435 124.121103,123.157921 L125.256707,122.986046 C125.354922,122.96763 125.428583,122.857139 125.428583,122.765063 L125.428583,121.402338 Z" transform="translate(-116 -117)" fill="#666" fill-rule="evenodd"/>
</defs>
<use id="close" xlink:href="#close-shape"/>
<use id="close-active" xlink:href="#close-shape"/>
@ -48,4 +49,5 @@
<use id="minimize" xlink:href="#minimize-shape"/>
<use id="minimize-active" xlink:href="#minimize-shape"/>
<use id="minimize-disabled" xlink:href="#minimize-shape"/>
<use id="settings-cog" xlink:href="#cog-shape"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill-rule="evenodd" fill="#131311" d="M14.77,8c0,0.804,0.262,1.548,0.634,1.678L16,9.887 c-0.205,0.874-0.553,1.692-1.011,2.434l-0.567-0.272c-0.355-0.171-1.066,0.17-1.635,0.738c-0.569,0.569-0.909,1.279-0.738,1.635 l0.273,0.568c-0.741,0.46-1.566,0.79-2.438,0.998l-0.205-0.584c-0.13-0.372-0.874-0.634-1.678-0.634s-1.548,0.262-1.678,0.634 l-0.209,0.596c-0.874-0.205-1.692-0.553-2.434-1.011l0.272-0.567c0.171-0.355-0.17-1.066-0.739-1.635 c-0.568-0.568-1.279-0.909-1.635-0.738l-0.568,0.273c-0.46-0.741-0.79-1.566-0.998-2.439l0.584-0.205 C0.969,9.547,1.231,8.804,1.231,8c0-0.804-0.262-1.548-0.634-1.678L0,6.112c0.206-0.874,0.565-1.685,1.025-2.427l0.554,0.266 c0.355,0.171,1.066-0.17,1.635-0.738c0.569-0.568,0.909-1.28,0.739-1.635L3.686,1.025c0.742-0.46,1.553-0.818,2.427-1.024 l0.209,0.596C6.453,0.969,7.197,1.23,8.001,1.23s1.548-0.262,1.678-0.634l0.209-0.596c0.874,0.205,1.692,0.553,2.434,1.011 l-0.272,0.567c-0.171,0.355,0.17,1.066,0.738,1.635c0.569,0.568,1.279,0.909,1.635,0.738l0.568-0.273 c0.46,0.741,0.79,1.566,0.998,2.438l-0.584,0.205C15.032,6.452,14.77,7.196,14.77,8z M8.001,3.661C5.604,3.661,3.661,5.603,3.661,8 c0,2.397,1.943,4.34,4.339,4.34c2.397,0,4.339-1.943,4.339-4.34C12.34,5.603,10.397,3.661,8.001,3.661z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -446,7 +446,11 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
// We can alias `subarray` to `slice` when the latter is not available, because
// they're semantically identical.
if (!Uint8Array.prototype.slice) {
/* eslint-disable */
// Eslint disabled for no-extend-native; Specific override needed for Firefox 37
// and earlier, also for other browsers.
Uint8Array.prototype.slice = Uint8Array.prototype.subarray;
/* eslint-enable */
}
/**
@ -736,6 +740,34 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
return obj;
}
/**
* Truncate a string if it exceeds the length as defined in `maxLen`, which
* is defined as '72' characters by default. If the string needs trimming,
* it'll be suffixed with the unicode ellipsis char, \u2026.
*
* @param {String} str The string to truncate, if needed.
* @param {Number} maxLen Maximum number of characters that the string is
* allowed to contain. Optional, defaults to 72.
* @return {String} Truncated version of `str`.
*/
function truncate(str, maxLen) {
maxLen = maxLen || 72;
if (str.length > maxLen) {
var substring = str.substr(0, maxLen);
// XXX Due to the fact that we have two different l10n libraries.
var direction = mozL10n.getDirection ? mozL10n.getDirection() :
mozL10n.language.direction;
if (direction === "rtl") {
return "…" + substring;
}
return substring + "…";
}
return str;
}
this.utils = {
CALL_TYPES: CALL_TYPES,
FAILURE_DETAILS: FAILURE_DETAILS,
@ -764,6 +796,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
strToUint8Array: strToUint8Array,
Uint8ArrayToStr: Uint8ArrayToStr,
objectDiff: objectDiff,
stripFalsyValues: stripFalsyValues
stripFalsyValues: stripFalsyValues,
truncate: truncate
};
}).call(inChrome ? this : loop.shared);

View File

@ -54,7 +54,6 @@ browser.jar:
content/browser/loop/shared/img/video-inverse-14x14@2x.png (content/shared/img/video-inverse-14x14@2x.png)
content/browser/loop/shared/img/dropdown-inverse.png (content/shared/img/dropdown-inverse.png)
content/browser/loop/shared/img/dropdown-inverse@2x.png (content/shared/img/dropdown-inverse@2x.png)
content/browser/loop/shared/img/svg/glyph-settings-16x16.svg (content/shared/img/svg/glyph-settings-16x16.svg)
content/browser/loop/shared/img/svg/glyph-account-16x16.svg (content/shared/img/svg/glyph-account-16x16.svg)
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)

View File

@ -75,4 +75,6 @@ config:
@echo "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';" >> content/config.js
@echo "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" >> content/config.js
@echo "loop.config.generalSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" >> content/config.js
@echo "loop.config.tilesIframeUrl = 'https://tiles.cdn.mozilla.net/iframe.html';" >> content/config.js
@echo "loop.config.tilesSupportUrl = 'https://support.mozilla.org/tiles-firefox-hello';" >> content/config.js
@echo "loop.config.unsupportedPlatformUrl = 'https://support.mozilla.org/en-US/kb/which-browsers-will-work-firefox-hello-video-chat';" >> content/config.js

View File

@ -51,6 +51,37 @@ body,
margin-top: 2rem;
}
/* Waiting info offer */
.standalone .empty-room-message {
font-size: 1.2em;
font-weight: bold;
}
.standalone .room-waiting-area {
display: flex;
justify-content: space-between;
margin: 3em auto 1em;
}
.standalone .room-waiting-help {
background: transparent url("../shared/img/svg/glyph-help-16x16.svg") no-repeat;
display: inline-block;
height: 16px;
margin-left: 5px;
vertical-align: middle;
width: 16px;
}
.standalone .room-waiting-tile {
border: 0;
border-radius: 5px;
/* These sizes are the size of the tile image and title */
height: 204px;
/* Override the default iframe 300px width with the inherited width */
width: 100%;
}
/*
* Top/Bottom spacing
**/

View File

@ -151,7 +151,17 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("div", {className: "room-inner-info-area"},
React.createElement("p", {className: "empty-room-message"},
mozL10n.get("rooms_only_occupant_label")
)
),
React.createElement("p", {className: "room-waiting-area"},
mozL10n.get("rooms_read_while_wait_offer"),
React.createElement("a", {href: loop.config.tilesSupportUrl,
onClick: this.recordClick,
rel: "noreferrer",
target: "_blank"},
React.createElement("i", {className: "room-waiting-help"})
)
),
React.createElement("iframe", {className: "room-waiting-tile", src: loop.config.tilesIframeUrl})
)
);
}
@ -461,13 +471,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("div", {className: "room-conversation-wrapper standalone-room-wrapper"},
React.createElement("div", {className: "beta-logo"}),
React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}),
React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore,
dispatcher: this.props.dispatcher,
failureReason: this.state.failureReason,
isFirefox: this.props.isFirefox,
joinRoom: this.joinRoom,
roomState: this.state.roomState,
roomUsed: this.state.used}),
React.createElement(sharedViews.MediaLayoutView, {
dispatcher: this.props.dispatcher,
displayScreenShare: displayScreenShare,
@ -484,7 +487,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
screenSharePosterUrl: this.props.screenSharePosterUrl,
screenShareVideoObject: this.state.screenShareVideoObject,
showContextRoomName: true,
useDesktopPaths: false}),
useDesktopPaths: false},
React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore,
dispatcher: this.props.dispatcher,
failureReason: this.state.failureReason,
isFirefox: this.props.isFirefox,
joinRoom: this.joinRoom,
roomState: this.state.roomState,
roomUsed: this.state.used})
),
React.createElement(sharedViews.ConversationToolbar, {
audio: {enabled: !this.state.audioMuted,
visible: this._roomIsActive()},

View File

@ -152,6 +152,16 @@ loop.standaloneRoomViews = (function(mozL10n) {
<p className="empty-room-message">
{mozL10n.get("rooms_only_occupant_label")}
</p>
<p className="room-waiting-area">
{mozL10n.get("rooms_read_while_wait_offer")}
<a href={loop.config.tilesSupportUrl}
onClick={this.recordClick}
rel="noreferrer"
target="_blank">
<i className="room-waiting-help"></i>
</a>
</p>
<iframe className="room-waiting-tile" src={loop.config.tilesIframeUrl} />
</div>
);
}
@ -461,13 +471,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
<div className="room-conversation-wrapper standalone-room-wrapper">
<div className="beta-logo" />
<StandaloneRoomHeader dispatcher={this.props.dispatcher} />
<StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
dispatcher={this.props.dispatcher}
failureReason={this.state.failureReason}
isFirefox={this.props.isFirefox}
joinRoom={this.joinRoom}
roomState={this.state.roomState}
roomUsed={this.state.used} />
<sharedViews.MediaLayoutView
dispatcher={this.props.dispatcher}
displayScreenShare={displayScreenShare}
@ -484,7 +487,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
screenSharePosterUrl={this.props.screenSharePosterUrl}
screenShareVideoObject={this.state.screenShareVideoObject}
showContextRoomName={true}
useDesktopPaths={false} />
useDesktopPaths={false}>
<StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
dispatcher={this.props.dispatcher}
failureReason={this.state.failureReason}
isFirefox={this.props.isFirefox}
joinRoom={this.joinRoom}
roomState={this.state.roomState}
roomUsed={this.state.used} />
</sharedViews.MediaLayoutView>
<sharedViews.ConversationToolbar
audio={{enabled: !this.state.audioMuted,
visible: this._roomIsActive()}}

View File

@ -111,6 +111,11 @@ rooms_media_denied_message=We could not get access to your microphone or camera.
room_information_failure_not_available=No information about this conversation is available. Please request a new link from the person who sent it to you.
room_information_failure_unsupported_browser=Your browser cannot access any information about this conversation. Please make sure you're using the latest version.
## LOCALIZATION_NOTE(rooms_read_while_wait_offer): This string is followed by a
# tile/offer image and title that are provided by a separate service that has
# localized content.
rooms_read_while_wait_offer=Want something to read while you wait?
## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
## replaced by the brand name and {{currentStatus}} will be replaced
## by the current call status (Connecting, Ringing, etc.)

View File

@ -12,8 +12,8 @@
},
"dependencies": {},
"devDependencies": {
"eslint": "0.24.x",
"eslint-plugin-react": "2.7.x",
"eslint": "1.0.x",
"eslint-plugin-react": "3.2.x",
"express": "4.x"
},
"scripts": {

View File

@ -12,6 +12,8 @@
var express = require("express");
var app = express();
var path = require("path");
var port = process.env.PORT || 3000;
var feedbackApiUrl = process.env.LOOP_FEEDBACK_API_URL ||
"https://input.allizom.org/api/v1/feedback";
@ -46,6 +48,8 @@ function getConfigFile(req, res) {
"loop.config.fxosApp.rooms = true;",
"loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';",
"loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';",
"loop.config.tilesIframeUrl = 'https://tiles.cdn.mozilla.net/iframe.html';",
"loop.config.tilesSupportUrl = 'https://support.mozilla.org/tiles-firefox-hello';",
"loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';",
"loop.config.generalSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';",
"loop.config.unsupportedPlatformUrl = 'https://support.mozilla.org/en-US/kb/which-browsers-will-work-firefox-hello-video-chat'"
@ -60,32 +64,35 @@ app.get("/content/c/config.js", getConfigFile);
// /ui - for the ui showcase
// /content - for the standalone files.
app.use("/ui", express.static(__dirname + "/../ui"));
app.use("/ui", express.static(path.join(__dirname, "..", "ui")));
app.use("/ui/loop/", express.static(path.join(__dirname, "..", "content")));
app.use("/ui/shared/", express.static(path.join(__dirname, "..", "content",
"shared")));
// This exists exclusively for the unit tests. They are served the
// whole loop/ directory structure and expect some files in the standalone directory.
app.use("/standalone/content", express.static(__dirname + "/content"));
app.use("/standalone/content", express.static(path.join(__dirname, "content")));
// We load /content this from both /content *and* /../content. The first one
// does what we need for running in the github loop-client context, the second one
// handles running in the hg repo under mozilla-central and is used so that the shared
// files are in the right location.
app.use("/content", express.static(__dirname + "/content"));
app.use("/content", express.static(__dirname + "/../content"));
app.use("/content", express.static(path.join(__dirname, "content")));
app.use("/content", express.static(path.join(__dirname, "..", "content")));
// These two are based on the above, but handle call urls, that have a /c/ in them.
app.use("/content/c", express.static(__dirname + "/content"));
app.use("/content/c", express.static(__dirname + "/../content"));
app.use("/content/c", express.static(path.join(__dirname, "content")));
app.use("/content/c", express.static(path.join(__dirname, "..", "content")));
// Two lines for the same reason as /content above.
app.use("/test", express.static(__dirname + "/test"));
app.use("/test", express.static(__dirname + "/../test"));
app.use("/test", express.static(path.join(__dirname, "test")));
app.use("/test", express.static(path.join(__dirname, "..", "test")));
// As we don't have hashes on the urls, the best way to serve the index files
// appears to be to be to closely filter the url and match appropriately.
function serveIndex(req, res) {
"use strict";
return res.sendfile(__dirname + "/content/index.html");
return res.sendfile(path.join(__dirname, "content", "index.html"));
}
app.get(/^\/content\/[\w\-]+$/, serveIndex);

View File

@ -72,7 +72,8 @@ describe("loop.panel", function() {
logOutFromFxA: sandbox.stub(),
notifyUITour: sandbox.stub(),
openURL: sandbox.stub(),
getSelectedTabMetadata: sandbox.stub()
getSelectedTabMetadata: sandbox.stub(),
userProfile: null
};
document.mozL10n.initialize(navigator.mozLoop);
@ -136,9 +137,9 @@ describe("loop.panel", function() {
navigator.mozLoop.doNotDisturb = true;
});
it("should toggle the value of mozLoop.doNotDisturb", function() {
it("should toggle mozLoop.doNotDisturb to false", function() {
var availableMenuOption = view.getDOMNode()
.querySelector(".dnd-make-available");
.querySelector(".status-available");
TestUtils.Simulate.click(availableMenuOption);
@ -147,7 +148,7 @@ describe("loop.panel", function() {
it("should toggle the dropdown menu", function() {
var availableMenuOption = view.getDOMNode()
.querySelector(".dnd-status span");
.querySelector(".dnd-status span");
TestUtils.Simulate.click(availableMenuOption);
@ -235,8 +236,7 @@ describe("loop.panel", function() {
});
});
describe("AuthLink", function() {
describe("AccountLink", function() {
beforeEach(function() {
navigator.mozLoop.calls = { clearCallInProgress: function() {} };
});
@ -249,13 +249,12 @@ describe("loop.panel", function() {
it("should trigger the FxA sign in/up process when clicking the link",
function() {
navigator.mozLoop.loggedInToFxA = false;
navigator.mozLoop.logInToFxA = sandbox.stub();
var view = createTestPanelView();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".signin-link a"));
view.getDOMNode().querySelector(".signin-link > a"));
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
@ -268,7 +267,7 @@ describe("loop.panel", function() {
var view = createTestPanelView();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".signin-link a"));
view.getDOMNode().querySelector(".signin-link > a"));
sinon.assert.calledOnce(fakeWindow.close);
});
@ -277,9 +276,50 @@ describe("loop.panel", function() {
function() {
navigator.mozLoop.fxAEnabled = false;
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.AuthLink));
React.createElement(loop.panel.AccountLink, {
fxAEnabled: false,
userProfile: null
}));
expect(view.getDOMNode()).to.be.null;
});
it("should add ellipsis to text over 24chars", function() {
navigator.mozLoop.userProfile = {
email: "reallyreallylongtext@example.com"
};
var view = createTestPanelView();
var node = view.getDOMNode().querySelector(".user-identity");
expect(node.textContent).to.eql("reallyreallylongtext@exa…");
});
it("should throw an error when user profile is different from {} or null",
function() {
var warnstub = sandbox.stub(console, "warn");
var view = TestUtils.renderIntoDocument(React.createElement(
loop.panel.AccountLink, {
fxAEnabled: false,
userProfile: []
}
));
sinon.assert.calledOnce(warnstub);
sinon.assert.calledWithExactly(warnstub, "Warning: Required prop `userProfile` was not correctly specified in `AccountLink`.");
});
it("should throw an error when user profile is different from {} or null",
function() {
var warnstub = sandbox.stub(console, "warn");
var view = TestUtils.renderIntoDocument(React.createElement(
loop.panel.AccountLink, {
fxAEnabled: false,
userProfile: function() {}
}
));
sinon.assert.calledOnce(warnstub);
sinon.assert.calledWithExactly(warnstub, "Warning: Required prop `userProfile` was not correctly specified in `AccountLink`.");
});
});
describe("SettingsDropdown", function() {
@ -300,17 +340,39 @@ describe("loop.panel", function() {
navigator.mozLoop.fxAEnabled = true;
});
it("should show a signin entry when user is not authenticated",
function() {
navigator.mozLoop.loggedInToFxA = false;
describe("UserLoggedOut", function() {
beforeEach(function() {
fakeMozLoop.userProfile = null;
});
it("should show a signin entry when user is not authenticated",
function() {
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-signout"))
.to.have.length.of(0);
expect(view.getDOMNode().querySelectorAll(".icon-signin"))
.to.have.length.of(1);
});
it("should hide any account entry when user is not authenticated",
function() {
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-account"))
.to.have.length.of(0);
});
it("should sign in the user on click when unauthenticated", function() {
navigator.mozLoop.loggedInToFxA = false;
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-signout"))
.to.have.length.of(0);
expect(view.getDOMNode().querySelectorAll(".icon-signin"))
.to.have.length.of(1);
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-signin"));
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
});
it("should show a signout entry when user is authenticated", function() {
navigator.mozLoop.userProfile = {email: "test@example.com"};
@ -332,43 +394,24 @@ describe("loop.panel", function() {
.to.have.length.of(1);
});
it("should open the FxA settings when the account entry is clicked", function() {
navigator.mozLoop.userProfile = {email: "test@example.com"};
it("should open the FxA settings when the account entry is clicked",
function() {
navigator.mozLoop.userProfile = {email: "test@example.com"};
var view = mountTestComponent();
var view = mountTestComponent();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".icon-account"));
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-account"));
sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
});
it("should hide any account entry when user is not authenticated",
function() {
navigator.mozLoop.loggedInToFxA = false;
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-account"))
.to.have.length.of(0);
});
it("should sign in the user on click when unauthenticated", function() {
navigator.mozLoop.loggedInToFxA = false;
var view = mountTestComponent();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".icon-signin"));
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
});
it("should sign out the user on click when authenticated", function() {
navigator.mozLoop.userProfile = {email: "test@example.com"};
var view = mountTestComponent();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".icon-signout"));
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-signout"));
sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
});
@ -724,7 +767,8 @@ describe("loop.panel", function() {
store: roomStore,
dispatcher: dispatcher,
userDisplayName: fakeEmail,
mozLoop: fakeMozLoop
mozLoop: fakeMozLoop,
userProfile: null
}));
}

View File

@ -152,7 +152,7 @@ describe("loop.store.RoomStore", function () {
expect(store.getStoreState().rooms).to.have.length.of(3);
expect(store.getStoreState().rooms.reduce(function(count, room) {
return count += room.roomToken === sampleRoom.roomToken ? 1 : 0;
return count + (room.roomToken === sampleRoom.roomToken ? 1 : 0);
}, 0)).eql(1);
});
});

View File

@ -289,8 +289,10 @@ add_task(function* basicAuthorizationAndRegistration() {
yield loadLoopPanel({stayOnline: true});
yield statusChangedPromise;
let loopDoc = document.getElementById("loop-panel-iframe").contentDocument;
let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
let accountLogin = loopDoc.getElementsByClassName("signin-link")[0];
let visibleEmail = loopDoc.getElementsByClassName("user-identity");
is(visibleEmail.length, 0, "No email should be displayed when logged out");
is(accountLogin.textContent, "Sign In or Sign Up", "Login/Signup links when logged out");
is(MozLoopService.userProfile, null, "profile should be null before log-in");
let loopButton = document.getElementById("loop-button");
is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
@ -303,6 +305,8 @@ add_task(function* basicAuthorizationAndRegistration() {
is(tokenData.scope, "profile", "Check scope");
is(tokenData.token_type, "bearer", "Check token_type");
visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
@ -328,7 +332,7 @@ add_task(function* basicAuthorizationAndRegistration() {
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response, null,
"Check registration was deleted on the server");
is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel again after logout");
is(accountLogin.textContent, "Sign In or Sign Up", "Login/Signup links when logged out");
is(MozLoopService.userProfile, null, "userProfile should be null after logout");
});

View File

@ -665,4 +665,45 @@ describe("loop.shared.utils", function() {
expect(obj).to.eql({ prop1: "null", prop3: true });
});
});
describe("#truncate", function() {
describe("ltr support", function() {
it("should default to 72 chars", function() {
var output = sharedUtils.truncate(new Array(75).join());
expect(output.length).to.eql(73); // 72 + …
});
it("should take a max size argument", function() {
var output = sharedUtils.truncate(new Array(73).join(), 20);
expect(output.length).to.eql(21); // 20 + …
});
});
describe("rtl support", function() {
var directionStub;
beforeEach(function() {
// XXX should use sandbox
// https://github.com/cjohansen/Sinon.JS/issues/781
directionStub = sinon.stub(navigator.mozL10n.language, "direction", {
get: function() {
return "rtl";
}
});
});
afterEach(function() {
directionStub.restore();
});
it("should support RTL", function() {
var output = sharedUtils.truncate(new Array(73).join(), 20);
expect(output.length).to.eql(21); // 20 + …
expect(output.substr(0, 1)).to.eql("…");
});
});
});
});

View File

@ -181,6 +181,20 @@ describe("loop.standaloneRoomViews", function() {
.not.eql(null);
});
it("should display a waiting room message and tile iframe on JOINED",
function() {
var DUMMY_TILE_URL = "http://tile/";
loop.config.tilesIframeUrl = DUMMY_TILE_URL;
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
expect(view.getDOMNode().querySelector(".room-waiting-area"))
.not.eql(null);
var tile = view.getDOMNode().querySelector(".room-waiting-tile");
expect(tile).not.eql(null);
expect(tile.src).eql(DUMMY_TILE_URL);
});
it("should display an empty room message on SESSION_CONNECTED",
function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});

View File

@ -14,7 +14,13 @@
navigator.mozL10n = document.mozL10n = {
initialize: function(){},
getDirection: function(){},
getDirection: function(){
if (document.location.search === "?rtl=1") {
return "rtl";
}
return "ltr";
},
get: function(stringId, vars) {

View File

@ -164,6 +164,7 @@ var fakeContacts = [{
},
fxAEnabled: true,
startAlerting: function() {},
stopAlerting: function() {}
stopAlerting: function() {},
userProfile: null
};
})();

View File

@ -129,6 +129,9 @@ body {
max-width: 120px;
}
.room-waiting-tile {
background-color: grey;
}
/* SVG icons showcase */
.svg-icons h3 {
@ -162,3 +165,9 @@ body {
.standalone.text-chat-example .text-chat-view {
height: 400px;
}
/* Force dropdown menus to display. */
.force-menu-show * {
display: inline-block !important;
}

View File

@ -15,6 +15,7 @@
// 1. Desktop components
// 1.1 Panel
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
// 1.2. Conversation Window
@ -438,7 +439,17 @@
// Local mocks
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
mockMozLoopLoggedIn.userProfile = {
email: "text@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
mockMozLoopLoggedInLongEmail.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockContact = {
name: ["Mr Smith"],
@ -492,7 +503,8 @@
"10x10": ["close", "close-active", "close-disabled", "dropdown",
"dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
"edit-active", "edit-disabled", "edit-white", "expand", "expand-active",
"expand-disabled", "minimize", "minimize-active", "minimize-disabled"
"expand-disabled", "minimize", "minimize-active", "minimize-disabled",
"settings-cog"
],
"14x14": ["audio", "audio-active", "audio-disabled", "facemute",
"facemute-active", "facemute-disabled", "hangup", "hangup-active",
@ -509,7 +521,8 @@
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",
"tag-hover", "tag-active", "trash", "unblock", "unblock-hover", "unblock-active",
"video", "video-hover", "video-active", "tour"
"video", "video-hover", "video-active", "tour", "status-available",
"status-unavailable"
]
},
@ -580,6 +593,7 @@
React.PropTypes.element,
React.PropTypes.arrayOf(React.PropTypes.element)
]).isRequired,
cssClass: React.PropTypes.string,
dashed: React.PropTypes.bool,
style: React.PropTypes.object,
summary: React.PropTypes.string.isRequired
@ -591,8 +605,14 @@
render: function() {
var cx = React.addons.classSet;
var extraCSSClass = {
"example": true
};
if (this.props.cssClass) {
extraCSSClass[this.props.cssClass] = true;
}
return (
React.createElement("div", {className: "example"},
React.createElement("div", {className: cx(extraCSSClass)},
React.createElement("h3", {id: this.makeId()},
this.props.summary,
React.createElement("a", {href: this.makeId("#")}, " ¶")
@ -693,25 +713,31 @@
React.createElement("strong", null, "Note:"), " 332px wide."
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Re-sign-in view"},
React.createElement(SignInRequestView, {mozLoop: mockMozLoopRooms})
React.createElement(SignInRequestView, {mozLoop: mockMozLoopLoggedIn})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Room list tab"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopRooms,
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "rooms",
userProfile: {email: "test@example.com"}})
selectedTab: "rooms"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopRooms,
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts",
userProfile: {email: "test@example.com"}})
selectedTab: "contacts"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab long email"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedInLongEmail,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"},
React.createElement(PanelView, {client: mockClient,
@ -723,26 +749,38 @@
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification - authenticated"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: navigator.mozLoop,
mozLoop: mockMozLoopLoggedIn,
notifications: errNotifications,
roomStore: roomStore,
userProfile: {email: "test@example.com"}})
roomStore: roomStore})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import success"},
React.createElement(PanelView, {dispatcher: dispatcher,
mozLoop: mockMozLoopRooms,
mozLoop: mockMozLoopLoggedIn,
notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]),
roomStore: roomStore,
selectedTab: "contacts",
userProfile: {email: "test@example.com"}})
selectedTab: "contacts"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import error"},
React.createElement(PanelView, {dispatcher: dispatcher,
mozLoop: mockMozLoopRooms,
mozLoop: mockMozLoopLoggedIn,
notifications: new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}]),
roomStore: roomStore,
selectedTab: "contacts",
userProfile: {email: "test@example.com"}})
selectedTab: "contacts"})
)
),
React.createElement(Section, {name: "Availability Dropdown"},
React.createElement("p", {className: "note"},
React.createElement("strong", null, "Note:"), " 332px wide."
),
React.createElement(Example, {dashed: true, style: {width: "332px", height: "200px"},
summary: "AvailabilityDropdown"},
React.createElement(AvailabilityDropdown, null)
),
React.createElement(Example, {cssClass: "force-menu-show", dashed: true,
style: {width: "332px", height: "200px"},
summary: "AvailabilityDropdown Expanded"},
React.createElement(AvailabilityDropdown, null)
)
),
@ -753,7 +791,7 @@
React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "Mr Smith",
dispatcher: dispatcher,
mozLoop: mockMozLoopRooms})
mozLoop: mockMozLoopLoggedIn})
)
),
@ -763,7 +801,7 @@
React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_ONLY,
callerId: "Mr Smith",
dispatcher: dispatcher,
mozLoop: mockMozLoopRooms})
mozLoop: mockMozLoopLoggedIn})
)
)
),
@ -775,7 +813,7 @@
React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO,
callerId: "Mr Smith",
dispatcher: dispatcher,
mozLoop: mockMozLoopRooms,
mozLoop: mockMozLoopLoggedIn,
showMenu: true})
)
)

View File

@ -15,6 +15,7 @@
// 1. Desktop components
// 1.1 Panel
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
// 1.2. Conversation Window
@ -438,7 +439,17 @@
// Local mocks
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
mockMozLoopLoggedIn.userProfile = {
email: "text@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
mockMozLoopLoggedInLongEmail.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockContact = {
name: ["Mr Smith"],
@ -492,7 +503,8 @@
"10x10": ["close", "close-active", "close-disabled", "dropdown",
"dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
"edit-active", "edit-disabled", "edit-white", "expand", "expand-active",
"expand-disabled", "minimize", "minimize-active", "minimize-disabled"
"expand-disabled", "minimize", "minimize-active", "minimize-disabled",
"settings-cog"
],
"14x14": ["audio", "audio-active", "audio-disabled", "facemute",
"facemute-active", "facemute-disabled", "hangup", "hangup-active",
@ -509,7 +521,8 @@
"precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
"settings", "settings-hover", "settings-active", "share-darkgrey", "tag",
"tag-hover", "tag-active", "trash", "unblock", "unblock-hover", "unblock-active",
"video", "video-hover", "video-active", "tour"
"video", "video-hover", "video-active", "tour", "status-available",
"status-unavailable"
]
},
@ -580,6 +593,7 @@
React.PropTypes.element,
React.PropTypes.arrayOf(React.PropTypes.element)
]).isRequired,
cssClass: React.PropTypes.string,
dashed: React.PropTypes.bool,
style: React.PropTypes.object,
summary: React.PropTypes.string.isRequired
@ -591,8 +605,14 @@
render: function() {
var cx = React.addons.classSet;
var extraCSSClass = {
"example": true
};
if (this.props.cssClass) {
extraCSSClass[this.props.cssClass] = true;
}
return (
<div className="example">
<div className={cx(extraCSSClass)}>
<h3 id={this.makeId()}>
{this.props.summary}
<a href={this.makeId("#")}>&nbsp;</a>
@ -693,25 +713,31 @@
<strong>Note:</strong> 332px wide.
</p>
<Example dashed={true} style={{width: "332px"}} summary="Re-sign-in view">
<SignInRequestView mozLoop={mockMozLoopRooms} />
<SignInRequestView mozLoop={mockMozLoopLoggedIn} />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Room list tab">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopRooms}
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="rooms"
userProfile={{email: "test@example.com"}} />
selectedTab="rooms" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Contact list tab">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopRooms}
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts"
userProfile={{email: "test@example.com"}} />
selectedTab="contacts" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Contact list tab long email">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedInLongEmail}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Error Notification">
<PanelView client={mockClient}
@ -723,26 +749,38 @@
<Example dashed={true} style={{width: "332px"}} summary="Error Notification - authenticated">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={navigator.mozLoop}
mozLoop={mockMozLoopLoggedIn}
notifications={errNotifications}
roomStore={roomStore}
userProfile={{email: "test@example.com"}} />
roomStore={roomStore} />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Contact import success">
<PanelView dispatcher={dispatcher}
mozLoop={mockMozLoopRooms}
mozLoop={mockMozLoopLoggedIn}
notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
roomStore={roomStore}
selectedTab="contacts"
userProfile={{email: "test@example.com"}} />
selectedTab="contacts" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Contact import error">
<PanelView dispatcher={dispatcher}
mozLoop={mockMozLoopRooms}
mozLoop={mockMozLoopLoggedIn}
notifications={new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}])}
roomStore={roomStore}
selectedTab="contacts"
userProfile={{email: "test@example.com"}} />
selectedTab="contacts" />
</Example>
</Section>
<Section name="Availability Dropdown">
<p className="note">
<strong>Note:</strong> 332px wide.
</p>
<Example dashed={true} style={{width: "332px", height: "200px"}}
summary="AvailabilityDropdown">
<AvailabilityDropdown />
</Example>
<Example cssClass="force-menu-show" dashed={true}
style={{width: "332px", height: "200px"}}
summary="AvailabilityDropdown Expanded">
<AvailabilityDropdown />
</Example>
</Section>
@ -753,7 +791,7 @@
<AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
callerId="Mr Smith"
dispatcher={dispatcher}
mozLoop={mockMozLoopRooms} />
mozLoop={mockMozLoopLoggedIn} />
</div>
</Example>
@ -763,7 +801,7 @@
<AcceptCallView callType={CALL_TYPES.AUDIO_ONLY}
callerId="Mr Smith"
dispatcher={dispatcher}
mozLoop={mockMozLoopRooms} />
mozLoop={mockMozLoopLoggedIn} />
</div>
</Example>
</Section>
@ -775,7 +813,7 @@
<AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
callerId="Mr Smith"
dispatcher={dispatcher}
mozLoop={mockMozLoopRooms}
mozLoop={mockMozLoopLoggedIn}
showMenu={true} />
</div>
</Example>

View File

@ -119,6 +119,7 @@ let gSyncPane = {
"weave:service:setup-complete",
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
FxAccountsCommon.ONLOGIN_NOTIFICATION,
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
let migrateTopic = "fxa-migration:state-changed";

View File

@ -33,13 +33,13 @@ const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.propertie
const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties";
const canvasDebuggerProps = "chrome://browser/locale/devtools/canvasdebugger.properties";
const webAudioEditorProps = "chrome://browser/locale/devtools/webaudioeditor.properties";
const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
const performanceProps = "chrome://browser/locale/devtools/performance.properties";
const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
const storageProps = "chrome://browser/locale/devtools/storage.properties";
const scratchpadProps = "chrome://browser/locale/devtools/scratchpad.properties";
loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps));
loader.lazyGetter(this, "performanceStrings",() => Services.strings.createBundle(performanceProps));
loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
@ -254,14 +254,14 @@ Tools.performance = {
highlightedicon: "chrome://browser/skin/devtools/tool-profiler-active.svg",
url: "chrome://browser/content/devtools/performance.xul",
visibilityswitch: "devtools.performance.enabled",
label: l10n("profiler.label2", profilerStrings),
panelLabel: l10n("profiler.panelLabel2", profilerStrings),
label: l10n("performance.label", performanceStrings),
panelLabel: l10n("performance.panelLabel", performanceStrings),
get tooltip() {
return l10n("profiler.tooltip3", profilerStrings,
return l10n("performance.tooltip", performanceStrings,
"Shift+" + functionkey(this.key));
},
accesskey: l10n("profiler.accesskey", profilerStrings),
key: l10n("profiler.commandkey2", profilerStrings),
accesskey: l10n("performance.accesskey", performanceStrings),
key: l10n("performance.commandkey", performanceStrings),
modifiers: "shift",
inMenu: true,

View File

@ -7,11 +7,10 @@ const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
/**
* Localization convenience methods.
+ TODO: merge these into a single file: Bug 1082695.
*/
const L10N = new ViewHelpers.MultiL10N([
"chrome://browser/locale/devtools/timeline.properties",
"chrome://browser/locale/devtools/profiler.properties"
"chrome://browser/locale/devtools/markers.properties",
"chrome://browser/locale/devtools/performance.properties"
]);
/**

View File

@ -93,12 +93,8 @@ function getMarkerFields (marker) {
// If blueprint.fields is a function, use that
if (typeof blueprint.fields === "function") {
let fields = blueprint.fields(marker);
// Add a ":" to the label since the localization files contain the ":"
// if not present. This should be changed, ugh.
return Object.keys(fields || []).map(label => {
// TODO revisit localization strings for markers bug 1163763
let normalizedLabel = label.indexOf(":") !== -1 ? label : (label + ":");
return { label: normalizedLabel, value: fields[label] };
return { label, value: fields[label] };
});
}
@ -168,7 +164,7 @@ const DOM = {
* @return {Element}
*/
buildDuration: function (doc, marker) {
let label = L10N.getStr("timeline.markerDetail.duration");
let label = L10N.getStr("marker.field.duration");
let start = L10N.getFormatStrWithNumbers("timeline.tick", marker.start);
let end = L10N.getFormatStrWithNumbers("timeline.tick", marker.end);
let duration = L10N.getFormatStrWithNumbers("timeline.tick", marker.end - marker.start);
@ -217,7 +213,7 @@ const DOM = {
let container = doc.createElement("vbox");
let labelName = doc.createElement("label");
labelName.className = "plain marker-details-labelname";
labelName.setAttribute("value", L10N.getStr(`timeline.markerDetail.${type}`));
labelName.setAttribute("value", L10N.getStr(`marker.field.${type}`));
container.setAttribute("type", type);
container.className = "marker-details-stack";
container.appendChild(labelName);
@ -235,7 +231,7 @@ const DOM = {
let asyncBox = doc.createElement("hbox");
let asyncLabel = doc.createElement("label");
asyncLabel.className = "devtools-monospace";
asyncLabel.setAttribute("value", L10N.getFormatStr("timeline.markerDetail.asyncStack",
asyncLabel.setAttribute("value", L10N.getFormatStr("marker.field.asyncStack",
frame.asyncCause));
asyncBox.appendChild(asyncLabel);
container.appendChild(asyncBox);
@ -278,7 +274,7 @@ const DOM = {
if (!displayName && !url) {
let label = doc.createElement("label");
label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
label.setAttribute("value", L10N.getStr("marker.value.unknownFrame"));
hbox.appendChild(label);
}
@ -301,18 +297,19 @@ const DOM = {
* markers that are considered "from content" should be labeled here.
*/
const JS_MARKER_MAP = {
"<script> element": "Script Tag",
"<script> element": L10N.getStr("marker.label.javascript.scriptElement"),
"promise callback": L10N.getStr("marker.label.javascript.promiseCallback"),
"promise initializer": L10N.getStr("marker.label.javascript.promiseInit"),
"Worker runnable": L10N.getStr("marker.label.javascript.workerRunnable"),
"javascript: URI": L10N.getStr("marker.label.javascript.jsURI"),
// The difference between these two event handler markers are differences
// in their WebIDL implementation, so distinguishing them is not necessary.
"EventHandlerNonNull": L10N.getStr("marker.label.javascript.eventHandler"),
"EventListener.handleEvent": L10N.getStr("marker.label.javascript.eventHandler"),
// These markers do not get L10N'd because they're JS names.
"setInterval handler": "setInterval",
"setTimeout handler": "setTimeout",
"FrameRequestCallback": "requestAnimationFrame",
"promise callback": "Promise Callback",
"promise initializer": "Promise Init",
"Worker runnable": "Worker",
"javascript: URI": "JavaScript URI",
// The difference between these two event handler markers are differences
// in their WebIDL implementation, so distinguishing them is not necessary.
"EventHandlerNonNull": "Event Handler",
"EventListener.handleEvent": "Event Handler",
};
/**
@ -324,21 +321,20 @@ const Formatters = {
* a blueprint entry. Uses "Other" in the marker filter menu.
*/
UnknownLabel: function (marker={}) {
return marker.name || L10N.getStr("timeline.label.unknown");
return marker.name || L10N.getStr("marker.label.unknown");
},
GCLabel: function (marker={}) {
let label = L10N.getStr("timeline.label.garbageCollection");
// Only if a `nonincrementalReason` exists, do we want to label
// this as a non incremental GC event.
if ("nonincrementalReason" in marker) {
label = `${label} (Non-incremental)`;
return L10N.getStr("marker.label.garbageCollection.nonIncremental");
}
return label;
return L10N.getStr("marker.label.garbageCollection");
},
JSLabel: function (marker={}) {
let generic = L10N.getStr("timeline.label.javascript2");
let generic = L10N.getStr("marker.label.javascript");
if ("causeName" in marker) {
return JS_MARKER_MAP[marker.causeName] || generic;
}
@ -357,40 +353,44 @@ const Formatters = {
*/
JSFields: function (marker) {
if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) {
return { Reason: PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL };
let cause = PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL;
return {
[L10N.getStr("marker.field.causeName")]: cause
};
}
},
DOMEventFields: function (marker) {
let fields = Object.create(null);
if ("type" in marker) {
fields[L10N.getStr("timeline.markerDetail.DOMEventType")] = marker.type;
fields[L10N.getStr("marker.field.DOMEventType")] = marker.type;
}
if ("eventPhase" in marker) {
let phase;
if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) {
phase = L10N.getStr("timeline.markerDetail.DOMEventTargetPhase");
phase = L10N.getStr("marker.value.DOMEventTargetPhase");
} else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) {
phase = L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase");
phase = L10N.getStr("marker.value.DOMEventCapturingPhase");
} else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) {
phase = L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase");
phase = L10N.getStr("marker.value.DOMEventBubblingPhase");
}
fields[L10N.getStr("timeline.markerDetail.DOMEventPhase")] = phase;
fields[L10N.getStr("marker.field.DOMEventPhase")] = phase;
}
return fields;
},
StylesFields: function (marker) {
if ("restyleHint" in marker) {
return { "Restyle Hint": marker.restyleHint.replace(/eRestyle_/g, "") };
return {
[L10N.getStr("marker.field.restyleHint")]: marker.restyleHint.replace(/eRestyle_/g, "")
};
}
},
CycleCollectionFields: function (marker) {
let Type = PREFS["show-platform-data"]
? marker.name
: marker.name.replace(/nsCycleCollector::/g, "");
return { Type };
return {
[L10N.getStr("marker.field.type")]: marker.name.replace(/nsCycleCollector::/g, "")
};
},
};

View File

@ -371,9 +371,8 @@ RecordingModel.prototype = {
case "allocations": {
if (!config.withAllocations) { break; }
let [{ sites, timestamps, frames, counts }] = data;
let timeOffset = this._memoryStartTime * 1000;
let timeScale = 1000;
RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset, timeScale);
let timeOffset = this._memoryStartTime;
RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset);
pushAll(this._allocations.sites, sites);
pushAll(this._allocations.timestamps, timestamps);
pushAll(this._allocations.frames, frames);

View File

@ -106,7 +106,9 @@ function offsetMarkerTimes(markers, timeOffset) {
function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
for (let i = 0, len = timestamps.length; i < len; i++) {
timestamps[i] -= timeOffset;
timestamps[i] /= timeScale;
if (timeScale) {
timestamps[i] /= timeScale;
}
}
}

View File

@ -10,7 +10,7 @@ const { Formatters } = require("devtools/performance/marker-utils");
* A simple schema for mapping markers to the timeline UI. The keys correspond
* to marker names, while the values are objects with the following format:
*
* - group: The row index in the timeline overview graph; multiple markers
* - group: The row index in the overview graph; multiple markers
* can be added on the same row. @see <overview.js/buildGraphImage>
* - label: The label used in the waterfall to identify the marker. Can be a
* string or just a function that accepts the marker and returns a
@ -60,25 +60,25 @@ const TIMELINE_BLUEPRINT = {
"Styles": {
group: 0,
colorName: "graphs-purple",
label: L10N.getStr("timeline.label.styles2"),
label: L10N.getStr("marker.label.styles"),
fields: Formatters.StylesFields,
},
"Reflow": {
group: 0,
colorName: "graphs-purple",
label: L10N.getStr("timeline.label.reflow2"),
label: L10N.getStr("marker.label.reflow"),
},
"Paint": {
group: 0,
colorName: "graphs-green",
label: L10N.getStr("timeline.label.paint"),
label: L10N.getStr("marker.label.paint"),
},
/* Group 1 - JS */
"DOMEvent": {
group: 1,
colorName: "graphs-yellow",
label: L10N.getStr("timeline.label.domevent"),
label: L10N.getStr("marker.label.domevent"),
fields: Formatters.DOMEventFields,
},
"Javascript": {
@ -90,32 +90,32 @@ const TIMELINE_BLUEPRINT = {
"Parse HTML": {
group: 1,
colorName: "graphs-yellow",
label: L10N.getStr("timeline.label.parseHTML"),
label: L10N.getStr("marker.label.parseHTML"),
},
"Parse XML": {
group: 1,
colorName: "graphs-yellow",
label: L10N.getStr("timeline.label.parseXML"),
label: L10N.getStr("marker.label.parseXML"),
},
"GarbageCollection": {
group: 1,
colorName: "graphs-red",
label: Formatters.GCLabel,
fields: [
{ property: "causeName", label: "Reason:" },
{ property: "nonincrementalReason", label: "Non-incremental Reason:" }
{ property: "causeName", label: L10N.getStr("marker.field.causeName") },
{ property: "nonincrementalReason", label: L10N.getStr("marker.field.nonIncrementalCause") }
],
},
"nsCycleCollector::Collect": {
group: 1,
colorName: "graphs-red",
label: "Cycle Collection",
label: L10N.getStr("marker.label.cycleCollection"),
fields: Formatters.CycleCollectionFields,
},
"nsCycleCollector::ForgetSkippable": {
group: 1,
colorName: "graphs-red",
label: "Cycle Collection",
label: L10N.getStr("marker.label.cycleCollection.forgetSkippable"),
fields: Formatters.CycleCollectionFields,
},
@ -123,10 +123,10 @@ const TIMELINE_BLUEPRINT = {
"ConsoleTime": {
group: 2,
colorName: "graphs-blue",
label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"),
label: sublabelForProperty(L10N.getStr("marker.label.consoleTime"), "causeName"),
fields: [{
property: "causeName",
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
label: L10N.getStr("marker.field.consoleTimerName")
}],
nestable: false,
collapsible: false,
@ -134,7 +134,7 @@ const TIMELINE_BLUEPRINT = {
"TimeStamp": {
group: 2,
colorName: "graphs-blue",
label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"),
label: sublabelForProperty(L10N.getStr("marker.label.timestamp"), "causeName"),
fields: [{
property: "causeName",
label: "Label:"

View File

@ -24,8 +24,8 @@ loader.lazyRequireGetter(this, "getColor",
"devtools/shared/theme", true);
loader.lazyRequireGetter(this, "ProfilerGlobal",
"devtools/performance/global");
loader.lazyRequireGetter(this, "TimelineGlobal",
"devtools/performance/global");
loader.lazyRequireGetter(this, "L10N",
"devtools/performance/global", true);
loader.lazyRequireGetter(this, "MarkersOverview",
"devtools/performance/markers-overview", true);
@ -124,7 +124,7 @@ FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
* The parent node holding the overview.
*/
function MemoryGraph(parent) {
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
PerformanceGraph.call(this, parent, L10N.getStr("graphs.memory"));
}
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {

View File

@ -11,8 +11,6 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "L10N",
"devtools/performance/global", true);
loader.lazyRequireGetter(this, "MarkerUtils",
"devtools/performance/marker-utils");

View File

@ -173,7 +173,7 @@ let PerformanceView = {
$container.setAttribute("buffer-status", "in-progress");
}
$bufferLabel.value = `Buffer ${percent}% full`;
$bufferLabel.value = L10N.getFormatStr("profiler.bufferFull", percent);
this.emit(EVENTS.UI_BUFFER_UPDATED, percent);
},
@ -289,8 +289,7 @@ let PerformanceView = {
*/
_onImportButtonClick: function(e) {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
// TODO localize? in bug 1163763
fp.init(window, "Import recording…", Ci.nsIFilePicker.modeOpen);
fp.init(window, L10N.getStr("recordingsList.importDialogTitle"), Ci.nsIFilePicker.modeOpen);
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");

View File

@ -8,8 +8,8 @@
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/performance.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
%profilerDTD;
<!ENTITY % performanceDTD SYSTEM "chrome://browser/locale/devtools/performance.dtd">
%performanceDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
@ -36,46 +36,46 @@
<menuitem id="option-show-platform-data"
type="checkbox"
data-pref="show-platform-data"
label="&profilerUI.showPlatformData;"
tooltiptext="&profilerUI.showPlatformData.tooltiptext;"/>
label="&performanceUI.showPlatformData;"
tooltiptext="&performanceUI.showPlatformData.tooltiptext;"/>
<menuitem id="option-enable-memory"
class="experimental-option"
type="checkbox"
data-pref="enable-memory"
label="&profilerUI.enableMemory;"
tooltiptext="&profilerUI.enableMemory.tooltiptext;"/>
label="&performanceUI.enableMemory;"
tooltiptext="&performanceUI.enableMemory.tooltiptext;"/>
<menuitem id="option-enable-allocations"
class="experimental-option"
type="checkbox"
data-pref="enable-allocations"
label="&profilerUI.enableAllocations;"
tooltiptext="&profilerUI.enableAllocations.tooltiptext;"/>
label="&performanceUI.enableAllocations;"
tooltiptext="&performanceUI.enableAllocations.tooltiptext;"/>
<menuitem id="option-enable-framerate"
type="checkbox"
data-pref="enable-framerate"
label="&profilerUI.enableFramerate;"
tooltiptext="&profilerUI.enableFramerate.tooltiptext;"/>
label="&performanceUI.enableFramerate;"
tooltiptext="&performanceUI.enableFramerate.tooltiptext;"/>
<menuitem id="option-invert-call-tree"
type="checkbox"
data-pref="invert-call-tree"
label="&profilerUI.invertTree;"
tooltiptext="&profilerUI.invertTree.tooltiptext;"/>
label="&performanceUI.invertTree;"
tooltiptext="&performanceUI.invertTree.tooltiptext;"/>
<menuitem id="option-invert-flame-graph"
type="checkbox"
data-pref="invert-flame-graph"
label="&profilerUI.invertFlameGraph;"
tooltiptext="&profilerUI.invertFlameGraph.tooltiptext;"/>
label="&performanceUI.invertFlameGraph;"
tooltiptext="&performanceUI.invertFlameGraph.tooltiptext;"/>
<menuitem id="option-flatten-tree-recursion"
type="checkbox"
data-pref="flatten-tree-recursion"
label="&profilerUI.flattenTreeRecursion;"
tooltiptext="&profilerUI.flattenTreeRecursion.tooltiptext;"/>
label="&performanceUI.flattenTreeRecursion;"
tooltiptext="&performanceUI.flattenTreeRecursion.tooltiptext;"/>
<menuitem id="option-enable-jit-optimizations"
class="experimental-option"
type="checkbox"
data-pref="enable-jit-optimizations"
label="&profilerUI.enableJITOptimizations;"
tooltiptext="&profilerUI.enableJITOptimizations.tooltiptext;"/>
label="&performanceUI.enableJITOptimizations;"
tooltiptext="&performanceUI.enableJITOptimizations.tooltiptext;"/>
</menupopup>
</popupset>
@ -89,13 +89,13 @@
class="devtools-toolbarbutton-group">
<toolbarbutton id="main-record-button"
class="devtools-toolbarbutton record-button"
tooltiptext="&profilerUI.recordButton2.tooltip;"/>
tooltiptext="&performanceUI.recordButton.tooltip;"/>
<toolbarbutton id="import-button"
class="devtools-toolbarbutton"
label="&profilerUI.importButton;"/>
label="&performanceUI.importButton;"/>
<toolbarbutton id="clear-button"
class="devtools-toolbarbutton"
label="&profilerUI.clearButton;"/>
label="&performanceUI.clearButton;"/>
</hbox>
</toolbar>
<vbox id="recordings-list" flex="1"/>
@ -112,33 +112,33 @@
<toolbarbutton id="filter-button"
class="devtools-toolbarbutton"
popup="performance-filter-menupopup"
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
tooltiptext="&performanceUI.options.filter.tooltiptext;"/>
</hbox>
<hbox id="performance-toolbar-controls-detail-views"
class="devtools-toolbarbutton-group">
<toolbarbutton id="select-waterfall-view"
class="devtools-toolbarbutton devtools-button"
label="Waterfall"
label="&performanceUI.toolbar.waterfall;"
hidden="true"
data-view="waterfall" />
<toolbarbutton id="select-js-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="Call Tree"
label="&performanceUI.toolbar.js-calltree;"
hidden="true"
data-view="js-calltree" />
<toolbarbutton id="select-js-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="Flame Chart"
label="&performanceUI.toolbar.js-flamegraph;"
hidden="true"
data-view="js-flamegraph" />
<toolbarbutton id="select-memory-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="Allocations Tree"
label="&performanceUI.toolbar.memory-calltree;"
hidden="true"
data-view="memory-calltree" />
<toolbarbutton id="select-memory-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="Allocations Chart"
label="&performanceUI.toolbar.memory-flamegraph;"
hidden="true"
data-view="memory-flamegraph" />
<toolbarbutton id="select-optimizations-view"
@ -153,7 +153,7 @@
<toolbarbutton id="performance-options-button"
class="devtools-toolbarbutton devtools-option-toolbarbutton"
popup="performance-options-menupopup"
tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
tooltiptext="&performanceUI.options.gear.tooltiptext;"/>
</hbox>
</toolbar>
@ -169,7 +169,7 @@
<hbox class="devtools-toolbarbutton-group"
pack="center">
<toolbarbutton class="devtools-toolbarbutton record-button"
label="&profilerUI.startRecording;"
label="&performanceUI.startRecording;"
standalone="true"/>
</hbox>
</hbox>
@ -193,7 +193,7 @@
align="center"
pack="center"
flex="1">
<label value="&profilerUI.loadingNotice;"/>
<label value="&performanceUI.loadingNotice;"/>
</hbox>
<!-- "Recording" notice, shown when a recording is in progress -->
@ -205,19 +205,17 @@
<hbox class="devtools-toolbarbutton-group"
pack="center">
<toolbarbutton class="devtools-toolbarbutton record-button"
label="&profilerUI.stopRecording;"
label="&performanceUI.stopRecording;"
standalone="true"/>
</hbox>
<label class="realtime-disabled-message">
Realtime recording data disabled on non-multiprocess Firefox.
</label>
<label class="realtime-disabled-on-e10s-message">
Enable multiprocess Firefox in preferences for rendering recording data in realtime.
</label>
<label class="realtime-disabled-message"
value="&performanceUI.disabledRealTime.nonE10SBuild;"/>
<label class="realtime-disabled-on-e10s-message"
value="&performanceUI.disabledRealTime.disabledE10S;"/>
<label class="buffer-status-message"
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
tooltiptext="&performanceUI.bufferStatusTooltip;"/>
<label class="buffer-status-message-full"
value="&profilerUI.bufferStatusFull;"/>
value="&performanceUI.bufferStatusFull;"/>
</vbox>
<!-- "Console" notice, shown when a console recording is in progress -->
@ -227,25 +225,23 @@
pack="center"
flex="1">
<hbox class="console-profile-recording-notice">
<label value="&profilerUI.console.recordingNoticeStart;" />
<label value="&performanceUI.console.recordingNoticeStart;" />
<label class="console-profile-command" />
<label value="&profilerUI.console.recordingNoticeEnd;" />
<label value="&performanceUI.console.recordingNoticeEnd;" />
</hbox>
<hbox class="console-profile-stop-notice">
<label value="&profilerUI.console.stopCommandStart;" />
<label value="&performanceUI.console.stopCommandStart;" />
<label class="console-profile-command" />
<label value="&profilerUI.console.stopCommandEnd;" />
<label value="&performanceUI.console.stopCommandEnd;" />
</hbox>
<label class="realtime-disabled-message">
Realtime recording data disabled on non-multiprocess Firefox.
</label>
<label class="realtime-disabled-on-e10s-message">
Enable multiprocess Firefox in preferences for rendering recording data in realtime.
</label>
<label class="realtime-disabled-message"
value="&performanceUI.disabledRealTime.nonE10SBuild;"/>
<label class="realtime-disabled-on-e10s-message"
value="&performanceUI.disabledRealTime.disabledE10S;"/>
<label class="buffer-status-message"
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
tooltiptext="&performanceUI.bufferStatusTooltip;"/>
<label class="buffer-status-message-full"
value="&profilerUI.bufferStatusFull;"/>
value="&performanceUI.bufferStatusFull;"/>
</vbox>
<!-- Detail views -->
@ -269,33 +265,33 @@
<label class="plain call-tree-header"
type="duration"
crop="end"
value="&profilerUI.table.totalDuration2;"
tooltiptext="&profilerUI.table.totalDuration.tooltip;"/>
value="&performanceUI.table.totalDuration;"
tooltiptext="&performanceUI.table.totalDuration.tooltip;"/>
<label class="plain call-tree-header"
type="percentage"
crop="end"
value="&profilerUI.table.totalPercentage;"
tooltiptext="&profilerUI.table.totalPercentage.tooltip;"/>
value="&performanceUI.table.totalPercentage;"
tooltiptext="&performanceUI.table.totalPercentage.tooltip;"/>
<label class="plain call-tree-header"
type="self-duration"
crop="end"
value="&profilerUI.table.selfDuration2;"
tooltiptext="&profilerUI.table.selfDuration.tooltip;"/>
value="&performanceUI.table.selfDuration;"
tooltiptext="&performanceUI.table.selfDuration.tooltip;"/>
<label class="plain call-tree-header"
type="self-percentage"
crop="end"
value="&profilerUI.table.selfPercentage;"
tooltiptext="&profilerUI.table.selfPercentage.tooltip;"/>
value="&performanceUI.table.selfPercentage;"
tooltiptext="&performanceUI.table.selfPercentage.tooltip;"/>
<label class="plain call-tree-header"
type="samples"
crop="end"
value="&profilerUI.table.samples;"
tooltiptext="&profilerUI.table.samples.tooltip;"/>
value="&performanceUI.table.samples;"
tooltiptext="&performanceUI.table.samples.tooltip;"/>
<label class="plain call-tree-header"
type="function"
crop="end"
value="&profilerUI.table.function;"
tooltiptext="&profilerUI.table.function.tooltip;"/>
value="&performanceUI.table.function;"
tooltiptext="&performanceUI.table.function.tooltip;"/>
</hbox>
<vbox class="call-tree-cells-container" flex="1"/>
</vbox>
@ -311,17 +307,17 @@
<label class="plain call-tree-header"
type="allocations"
crop="end"
value="&profilerUI.table.totalAlloc1;"
tooltiptext="&profilerUI.table.totalAlloc.tooltip;"/>
value="&performanceUI.table.totalAlloc;"
tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
<label class="plain call-tree-header"
type="self-allocations"
crop="end"
value="&profilerUI.table.selfAlloc1;"
tooltiptext="&profilerUI.table.selfAlloc.tooltip;"/>
value="&performanceUI.table.selfAlloc;"
tooltiptext="&performanceUI.table.selfAlloc.tooltip;"/>
<label class="plain call-tree-header"
type="function"
crop="end"
value="&profilerUI.table.function;"/>
value="&performanceUI.table.function;"/>
</hbox>
<vbox class="call-tree-cells-container" flex="1"/>
</vbox>
@ -352,7 +348,7 @@
<vbox id="jit-optimizations-view">
<toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
<hbox id="jit-optimizations-header">
<span class="jit-optimizations-title">&profilerUI.JITOptimizationsTitle;</span>
<span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
<span class="header-function-name" />
<span class="header-file opt-url debugger-link" />
<span class="header-line opt-line" />

View File

@ -2,6 +2,7 @@
tags = devtools
subsuite = devtools
support-files =
doc_allocs.html
doc_force_cc.html
doc_force_gc.html
doc_innerHTML.html

View File

@ -5,14 +5,14 @@
* Tests that the memory call tree view renders content after recording.
*/
function* spawnTest() {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
let { panel } = yield initPerformance(ALLOCS_URL);
let { EVENTS, $$, PerformanceController, DetailsView, MemoryCallTreeView } = panel.panelWin;
// Enable memory to test.
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
yield startRecording(panel);
yield busyWait(100);
yield waitUntil(() => PerformanceController.getCurrentRecording().getAllocations().timestamps.length);
yield stopRecording(panel);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
@ -22,6 +22,8 @@ function* spawnTest() {
ok(true, "MemoryCallTreeView rendered after recording is stopped.");
ok($$("#memory-calltree-view .call-tree-item").length, "there are several allocations rendered.");
yield startRecording(panel);
yield busyWait(100);

View File

@ -9,11 +9,7 @@
let test = Task.async(function*() {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
let { RecordingsView, PerformanceController, PerformanceView,
EVENTS, $, L10N, ViewHelpers } = panel.panelWin;
// This should be removed with bug 1163763.
let DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
let DBG_L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
EVENTS, $, L10N } = panel.panelWin;
info("Start to record");
yield startRecording(panel);
@ -34,7 +30,7 @@ let test = Task.async(function*() {
yield willStop;
is(durationNode.getAttribute("value"),
DBG_L10N.getStr("loadingText"),
L10N.getStr("recordingsList.loadingLabel"),
"The duration node should show the 'loading' message while stopping");
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);

View File

@ -0,0 +1,25 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Performance test page</title>
</head>
<body>
<script type="text/javascript">
var allocs = [];
function test() {
for (var i = 0; i < 10; i++) {
allocs.push({});
}
}
// Prevent this script from being garbage collected.
window.setInterval(test, 1);
</script>
</body>
</html>

View File

@ -24,6 +24,7 @@ const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-u
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/performance/test/";
const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
const MARKERS_URL = EXAMPLE_URL + "doc_markers.html";
const ALLOCS_URL = EXAMPLE_URL + "doc_allocs.html";
const MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
const MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";

View File

@ -47,7 +47,7 @@ add_task(function () {
equal(Utils.getMarkerClassName("Javascript"), "Function Call",
"getMarkerClassName() returns correct string when defined via function");
equal(Utils.getMarkerClassName("GarbageCollection"), "GC Event",
equal(Utils.getMarkerClassName("GarbageCollection"), "Incremental GC",
"getMarkerClassName() returns correct string when defined via function");
equal(Utils.getMarkerClassName("Reflow"), "Layout",
"getMarkerClassName() returns correct string when defined via string");
@ -60,7 +60,7 @@ add_task(function () {
ok(true, "getMarkerClassName() should throw when no label on blueprint.");
}
TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0};
TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0 };
try {
Utils.getMarkerClassName("fakemarker");
ok(false, "getMarkerClassName() should throw when label function returnd undefined.");

View File

@ -5,7 +5,7 @@
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
const JIT_SAMPLES = L10N.getStr("jit.samples2");
const JIT_SAMPLES = L10N.getStr("jit.samples");
const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
const PROPNAME_MAX_LENGTH = 4;

View File

@ -3,10 +3,6 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This should be removed with bug 1163763.
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const DBG_L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
/**
* Functions handling the recordings UI.
*/
@ -151,7 +147,7 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
// Mark the corresponding item as loading.
let durationNode = $(".recording-item-duration", recordingItem.target);
durationNode.setAttribute("value", DBG_L10N.getStr("loadingText"));
durationNode.setAttribute("value", L10N.getStr("recordingsList.loadingLabel"));
},
/**
@ -214,8 +210,7 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
*/
_onSaveButtonClick: function (e) {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
// TODO localize? in bug 1163763
fp.init(window, "Save recording…", Ci.nsIFilePicker.modeSave);
fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
fp.defaultString = "profile.json";

View File

@ -1025,6 +1025,7 @@ SwatchBasedEditorTooltip.prototype = {
* @param {object} callbacks
* Callbacks that will be executed when the editor wants to preview a
* value change, or revert a change, or commit a change.
* - onShow: will be called when one of the swatch tooltip is shown
* - onPreview: will be called when one of the sub-classes calls
* preview
* - onRevert: will be called when the user ESCapes out of the tooltip
@ -1032,6 +1033,9 @@ SwatchBasedEditorTooltip.prototype = {
* outside the tooltip.
*/
addSwatch: function(swatchEl, callbacks={}) {
if (!callbacks.onShow) {
callbacks.onShow = function() {};
}
if (!callbacks.onPreview) {
callbacks.onPreview = function() {};
}
@ -1069,6 +1073,7 @@ SwatchBasedEditorTooltip.prototype = {
if (swatch) {
this.activeSwatch = event.target;
this.show();
swatch.callbacks.onShow();
event.stopPropagation();
}
},

View File

@ -2849,6 +2849,9 @@ function TextPropertyEditor(aRuleEditor, aProperty) {
this._onStartEditing = this._onStartEditing.bind(this);
this._onNameDone = this._onNameDone.bind(this);
this._onValueDone = this._onValueDone.bind(this);
this._onSwatchCommit = this._onSwatchCommit.bind(this);
this._onSwatchPreview = this._onSwatchPreview.bind(this);
this._onSwatchRevert = this._onSwatchRevert.bind(this);
this._onValidate = throttle(this._previewValue, 10, this);
this.update = this.update.bind(this);
@ -3075,7 +3078,7 @@ TextPropertyEditor.prototype = {
this.warning.hidden = this.editing || this.isValid();
if ((this.prop.overridden || !this.prop.enabled) && !this.editing) {
if (this.prop.overridden || !this.prop.enabled) {
this.element.classList.add("ruleview-overridden");
} else {
this.element.classList.remove("ruleview-overridden");
@ -3130,9 +3133,10 @@ TextPropertyEditor.prototype = {
// Adding this swatch to the list of swatches our colorpicker
// knows about
this.ruleView.tooltips.colorPicker.addSwatch(span, {
onPreview: () => this._previewValue(this.valueSpan.textContent),
onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
onRevert: () => this._onValueDone(undefined, false)
onShow: this._onStartEditing,
onPreview: this._onSwatchPreview,
onCommit: this._onSwatchCommit,
onRevert: this._onSwatchRevert
});
}
}
@ -3145,9 +3149,10 @@ TextPropertyEditor.prototype = {
// Adding this swatch to the list of swatches our colorpicker
// knows about
this.ruleView.tooltips.cubicBezier.addSwatch(span, {
onPreview: () => this._previewValue(this.valueSpan.textContent),
onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
onRevert: () => this._onValueDone(undefined, false)
onShow: this._onStartEditing,
onPreview: this._onSwatchPreview,
onCommit: this._onSwatchCommit,
onRevert: this._onSwatchRevert
});
}
}
@ -3159,9 +3164,10 @@ TextPropertyEditor.prototype = {
parserOptions.filterSwatch = true;
this.ruleView.tooltips.filterEditor.addSwatch(span, {
onPreview: () => this._previewValue(this.valueSpan.textContent),
onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
onRevert: () => this._onValueDone(undefined, false)
onShow: this._onStartEditing,
onPreview: this._onSwatchPreview,
onCommit: this._onSwatchCommit,
onRevert: this._onSwatchRevert
}, outputParser, parserOptions);
}
}
@ -3422,6 +3428,30 @@ TextPropertyEditor.prototype = {
}
},
/**
* Called when the swatch editor wants to commit a value change.
*/
_onSwatchCommit: function() {
this._onValueDone(this.valueSpan.textContent, true);
this.update();
},
/**
* Called when the swatch editor wants to preview a value change.
*/
_onSwatchPreview: function() {
this._previewValue(this.valueSpan.textContent);
},
/**
* Called when the swatch editor closes from an ESC. Revert to the original
* value of this property before editing.
*/
_onSwatchRevert: function() {
this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
this.update();
},
/**
* Parse a value string and break it into pieces, starting with the
* first value, and into an array of additional properties (if any).

View File

@ -6,33 +6,32 @@
// Test that a color change in the color picker is reverted when ESC is pressed
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' background-color: #ededed;',
' }',
'</style>',
'Testing the color picker tooltip!'
let TEST_URI = [
"<style type='text/css'>",
" body {",
" background-color: #EDEDED;",
" }",
"</style>",
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8,rule view color picker tooltip test");
content.document.body.innerHTML = PAGE_CONTENT;
let {toolbox, inspector, view} = yield openRuleView();
let swatch = getRuleViewProperty(view, "body", "background-color").valueSpan
.querySelector(".ruleview-colorswatch");
yield testPressingEscapeRevertsChanges(swatch, view);
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {view} = yield openRuleView();
yield testPressingEscapeRevertsChanges(view);
yield testPressingEscapeRevertsChangesAndDisables(view);
});
function* testPressingEscapeRevertsChanges(swatch, ruleView) {
let cPicker = ruleView.tooltips.colorPicker;
function* testPressingEscapeRevertsChanges(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
let cPicker = view.tooltips.colorPicker;
let onShown = cPicker.tooltip.once("shown");
swatch.click();
yield onShown;
yield simulateColorPickerChange(ruleView, cPicker, [0, 0, 0, 1], {
yield simulateColorPickerChange(view, cPicker, [0, 0, 0, 1], {
element: content.document.body,
name: "backgroundColor",
value: "rgb(0, 0, 0)"
@ -40,17 +39,83 @@ function* testPressingEscapeRevertsChanges(swatch, ruleView) {
is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
"The color swatch's background was updated");
is(getRuleViewProperty(ruleView, "body", "background-color").valueSpan.textContent,
"#000", "The text of the background-color css property was updated");
is(propEditor.valueSpan.textContent, "#000",
"The text of the background-color css property was updated");
let spectrum = yield cPicker.spectrum;
// ESC out of the color picker
info("Pressing ESCAPE to close the tooltip");
let onHidden = cPicker.tooltip.once("hidden");
EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
yield onHidden;
yield ruleEditor.rule._applyingModifications;
yield waitForSuccess(() => {
return content.getComputedStyle(content.document.body).backgroundColor === "rgb(237, 237, 237)";
}, "The element's background-color was reverted");
yield waitForComputedStyleProperty("body", null, "background-color",
"rgb(237, 237, 237)");
is(propEditor.valueSpan.textContent, "#EDEDED",
"Got expected property value.");
}
function* testPressingEscapeRevertsChangesAndDisables(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
let cPicker = view.tooltips.colorPicker;
info("Disabling background-color property");
propEditor.enable.click();
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled,
"background-color property is disabled.");
let newValue = yield getRulePropertyValue("background-color");
is(newValue, "", "background-color should have been unset.");
let onShown = cPicker.tooltip.once("shown");
swatch.click();
yield onShown;
ok(!propEditor.element.classList.contains("ruleview-overridden"),
"property overridden is not displayed.");
is(propEditor.enable.style.visibility, "hidden",
"property enable checkbox is hidden.");
let spectrum = yield cPicker.spectrum;
info("Simulating a color picker change in the widget");
spectrum.rgb = [0, 0, 0, 1];
yield ruleEditor.rule._applyingModifications;
info("Pressing ESCAPE to close the tooltip");
let onHidden = cPicker.tooltip.once("hidden");
EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
yield onHidden;
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled,
"background-color property is disabled.");
newValue = yield getRulePropertyValue("background-color");
is(newValue, "", "background-color should have been unset.");
is(propEditor.valueSpan.textContent, "#EDEDED",
"Got expected property value.");
}
function* getRulePropertyValue(name) {
let propValue = yield executeInContent("Test:GetRulePropertyValue", {
styleSheetIndex: 0,
ruleIndex: 0,
name: name
});
return propValue;
}

View File

@ -4,30 +4,29 @@
"use strict";
// Test that changes made to the cubic-bezier timing-function in the cubic-bezier
// tooltip are reverted when ESC is pressed
// Test that changes made to the cubic-bezier timing-function in the
// cubic-bezier tooltip are reverted when ESC is pressed
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' animation-timing-function: linear;',
' }',
'</style>',
let TEST_URI = [
"<style type='text/css'>",
" body {",
" animation-timing-function: linear;",
" }",
"</style>",
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8,rule view cubic-bezier tooltip test");
content.document.body.innerHTML = PAGE_CONTENT;
let {toolbox, inspector, view} = yield openRuleView();
info("Getting the bezier swatch element");
let swatch = getRuleViewProperty(view, "body", "animation-timing-function").valueSpan
.querySelector(".ruleview-bezierswatch");
yield testPressingEscapeRevertsChanges(swatch, view);
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {view} = yield openRuleView();
yield testPressingEscapeRevertsChanges(view);
yield testPressingEscapeRevertsChangesAndDisables(view);
});
function* testPressingEscapeRevertsChanges(swatch, ruleView) {
let bezierTooltip = ruleView.tooltips.cubicBezier;
function* testPressingEscapeRevertsChanges(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
let bezierTooltip = view.tooltips.cubicBezier;
let onShown = bezierTooltip.tooltip.once("shown");
swatch.click();
@ -36,18 +35,85 @@ function* testPressingEscapeRevertsChanges(swatch, ruleView) {
let widget = yield bezierTooltip.widget;
info("Simulating a change of curve in the widget");
widget.coordinates = [0.1, 2, 0.9, -1];
let expected = "cubic-bezier(0.1, 2, 0.9, -1)";
yield ruleEditor.rule._applyingModifications;
yield waitForSuccess(() => {
return content.getComputedStyle(content.document.body).animationTimingFunction === expected;
}, "Waiting for the change to be previewed on the element");
yield waitForComputedStyleProperty("body", null, "animation-timing-function",
"cubic-bezier(0.1, 2, 0.9, -1)");
is(propEditor.valueSpan.textContent, "cubic-bezier(.1,2,.9,-1)",
"Got expected property value.");
info("Pressing ESCAPE to close the tooltip");
let onHidden = bezierTooltip.tooltip.once("hidden");
EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
yield onHidden;
yield ruleEditor.rule._applyingModifications;
yield waitForSuccess(() => {
return content.getComputedStyle(content.document.body).animationTimingFunction === "cubic-bezier(0, 0, 1, 1)";
}, "Waiting for the change to be reverted on the element");
yield waitForComputedStyleProperty("body", null, "animation-timing-function",
"cubic-bezier(0, 0, 1, 1)");
is(propEditor.valueSpan.textContent, "linear",
"Got expected property value.");
}
function* testPressingEscapeRevertsChangesAndDisables(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
let bezierTooltip = view.tooltips.cubicBezier;
info("Disabling animation-timing-function property");
propEditor.enable.click();
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled,
"animation-timing-function property is disabled.");
let newValue = yield getRulePropertyValue("animation-timing-function");
is(newValue, "", "animation-timing-function should have been unset.");
let onShown = bezierTooltip.tooltip.once("shown");
swatch.click();
yield onShown;
ok(!propEditor.element.classList.contains("ruleview-overridden"),
"property overridden is not displayed.");
is(propEditor.enable.style.visibility, "hidden",
"property enable checkbox is hidden.");
let widget = yield bezierTooltip.widget;
info("Simulating a change of curve in the widget");
widget.coordinates = [0.1, 2, 0.9, -1];
yield ruleEditor.rule._applyingModifications;
info("Pressing ESCAPE to close the tooltip");
let onHidden = bezierTooltip.tooltip.once("hidden");
EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
yield onHidden;
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled,
"animation-timing-function property is disabled.");
newValue = yield getRulePropertyValue("animation-timing-function");
is(newValue, "", "animation-timing-function should have been unset.");
is(propEditor.valueSpan.textContent, "linear",
"Got expected property value.");
}
function* getRulePropertyValue(name) {
let propValue = yield executeInContent("Test:GetRulePropertyValue", {
styleSheetIndex: 0,
ruleIndex: 0,
name: name
});
return propValue;
}

View File

@ -3,36 +3,102 @@
"use strict";
// Tests the Filter Editor Tooltip reverting changes on ESC
// Tests that changes made to the Filter Editor Tooltip are reverted when
// ESC is pressed
const TEST_URL = TEST_URL_ROOT + "doc_filter.html";
add_task(function*() {
yield addTab(TEST_URL);
let {view} = yield openRuleView();
yield testPressingEscapeRevertsChanges(view);
yield testPressingEscapeRevertsChangesAndDisables(view);
});
let {toolbox, inspector, view} = yield openRuleView();
info("Getting the filter swatch element");
let swatch = getRuleViewProperty(view, "body", "filter").valueSpan
.querySelector(".ruleview-filterswatch");
function* testPressingEscapeRevertsChanges(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
let filterTooltip = view.tooltips.filterEditor;
let onShow = filterTooltip.tooltip.once("shown");
swatch.click();
yield onShow;
let widget = yield filterTooltip.widget;
widget.setCssValue("blur(2px)");
yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)");
yield ruleEditor.rule._applyingModifications;
ok(true, "Changes previewed on the element");
yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)");
is(propEditor.valueSpan.textContent, "blur(2px)",
"Got expected property value.");
info("Pressing ESCAPE to close the tooltip");
EventUtils.sendKey("ESCAPE", widget.styleWindow);
yield ruleEditor.rule._applyingModifications;
yield waitForSuccess(() => {
const computed = content.getComputedStyle(content.document.body);
return computed.filter === "blur(2px) contrast(2)";
}, "Waiting for the change to be reverted on the element");
});
yield waitForComputedStyleProperty("body", null, "filter",
"blur(2px) contrast(2)");
is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
"Got expected property value.");
}
function* testPressingEscapeRevertsChangesAndDisables(view) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
let filterTooltip = view.tooltips.filterEditor;
info("Disabling filter property");
propEditor.enable.click();
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled,
"filter property is disabled.");
let newValue = yield getRulePropertyValue("filter");
is(newValue, "", "filter should have been unset.");
let onShow = filterTooltip.tooltip.once("shown");
swatch.click();
yield onShow;
ok(!propEditor.element.classList.contains("ruleview-overridden"),
"property overridden is not displayed.");
is(propEditor.enable.style.visibility, "hidden",
"property enable checkbox is hidden.");
let widget = yield filterTooltip.widget;
widget.setCssValue("blur(2px)");
yield ruleEditor.rule._applyingModifications;
info("Pressing ESCAPE to close the tooltip");
EventUtils.sendKey("ESCAPE", widget.styleWindow);
yield ruleEditor.rule._applyingModifications;
ok(propEditor.element.classList.contains("ruleview-overridden"),
"property is overridden.");
is(propEditor.enable.style.visibility, "visible",
"property enable checkbox is visible.");
ok(!propEditor.enable.getAttribute("checked"),
"property enable checkbox is not checked.");
ok(!propEditor.prop.enabled, "filter property is disabled.");
newValue = yield getRulePropertyValue("filter");
is(newValue, "", "filter should have been unset.");
is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
"Got expected property value.");
}
function* getRulePropertyValue(name) {
let propValue = yield executeInContent("Test:GetRulePropertyValue", {
styleSheetIndex: 0,
ruleIndex: 0,
name: name
});
return propValue;
}

View File

@ -0,0 +1,80 @@
# 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/.
# LOCALIZATION NOTE These strings are used inside the Performance Tools
# which is available from the Web Developer sub-menu -> 'Performance'.
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
# A good criteria is the language in which you'd find the best
# documentation on web development on the web. These strings
# are specifically for marker names in the performance tool.
# LOCALIZATION NOTE (marker.label.*):
# These strings are displayed in the Performance Tool waterfall, identifying markers.
# We want to use the same wording as Google Chrome when appropriate.
marker.label.styles=Recalculate Style
marker.label.reflow=Layout
marker.label.paint=Paint
marker.label.javascript=Function Call
marker.label.parseHTML=Parse HTML
marker.label.parseXML=Parse XML
marker.label.domevent=DOM Event
marker.label.consoleTime=Console
marker.label.garbageCollection=Incremental GC
marker.label.garbageCollection.nonIncremental=Non-incremental GC
marker.label.cycleCollection=Cycle Collection
marker.label.cycleCollection.forgetSkippable=CC Graph Reduction
marker.label.timestamp=Timestamp
marker.label.unknown=Unknown
# LOCALIZATION NOTE (marker.label.javascript.*):
# These strings are displayed as JavaScript markers that have special
# reasons that can be translated.
marker.label.javascript.scriptElement=Script Tag
marker.label.javascript.promiseCallback=Promise Callback
marker.label.javascript.promiseInit=Promise Init
marker.label.javascript.workerRunnable=Worker
marker.label.javascript.jsURI=JavaScript URI
marker.label.javascript.eventHandler=Event Handler
# LOCALIZATION NOTE (marker.fieldFormat):
# Some timeline markers come with details, like a size, a name, a js function.
# %1$S is replaced with one of the above label (marker.label.*) and %2$S
# with the details. For examples: Paint (200x100), or console.time (FOO)
marker.fieldFormat=%1$S (%2$S)
# LOCALIZATION NOTE (marker.field.*):
# Strings used in the waterfall sidebar as property names.
# General marker fields
marker.field.start=Start:
marker.field.end=End:
marker.field.duration=Duration:
# Field names for stack values
marker.field.stack=Stack:
marker.field.startStack=Stack at start:
marker.field.endStack=Stack at end:
# %S is the "Async Cause" of a marker, and this signifies that the cause
# was an asynchronous one in a displayed stack.
marker.field.asyncStack=(Async: %S)
# For console.time markers
marker.field.consoleTimerName=Timer Name:
# For DOM Event markers
marker.field.DOMEventType=Event Type:
marker.field.DOMEventPhase=Phase:
# Non-incremental cause for a Garbage Collection marker
marker.field.nonIncrementalCause=Non-incremental Cause:
# For "Recalculate Style" markers
marker.field.restyleHint=Restyle Hint:
# General "reason" for a marker (JavaScript, Garbage Collection)
marker.field.causeName=Cause:
# General "type" for a marker (Cycle Collection, Garbage Collection)
marker.field.type=Type:
# Strings used in the waterfall sidebar as values.
marker.value.unknownFrame=<unknown location>
marker.value.DOMEventTargetPhase=Target
marker.value.DOMEventCapturingPhase=Capture
marker.value.DOMEventBubblingPhase=Bubbling

View File

@ -0,0 +1,157 @@
<!-- 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/. -->
<!-- LOCALIZATION NOTE : FILE This file contains the Performance strings -->
<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
- keep it in English, or another language commonly spoken among web developers.
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!-- LOCALIZATION NOTE (performanceUI.startRecording/performanceUI.stopRecording): These are
- the labels shown on the main recording buttons to start/stop recording. -->
<!ENTITY performanceUI.startRecording "Start Recording Performance">
<!ENTITY performanceUI.stopRecording "Stop Recording Performance">
<!-- LOCALIZATION NOTE (performanceUI.bufferStatusTooltip): This string
- is displayed as the tooltip for the buffer capacity during a recording. -->
<!ENTITY performanceUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.nonE10SBuild): This string
- is displayed as a message for why the real time overview graph is disabled
- when running on a non-multiprocess build. -->
<!ENTITY performanceUI.disabledRealTime.nonE10SBuild "Realtime recording data disabled on non-multiprocess Firefox.">
<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.disabledE10S): This string
- is displayed as a message for why the real time overview graph is disabled
- when running on a build that can run multiprocess Firefox, but just is not enabled. -->
<!ENTITY performanceUI.disabledRealTime.disabledE10S "Enable multiprocess Firefox in preferences for rendering recording data in realtime.">
<!-- LOCALIZATION NOTE (performanceUI.bufferStatusFull): This string
- is displayed when the profiler's circular buffer has started to overlap. -->
<!ENTITY performanceUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown
- in the call list view while loading a profile. -->
<!ENTITY performanceUI.loadingNotice "Loading…">
<!-- LOCALIZATION NOTE (performanceUI.recordButton): This string is displayed
- on a button that starts a new profile. -->
<!ENTITY performanceUI.recordButton.tooltip "Toggle the recording state of a performance recording.">
<!-- LOCALIZATION NOTE (performanceUI.importButton): This string is displayed
- on a button that opens a dialog to import a saved profile data file. -->
<!ENTITY performanceUI.importButton "Import…">
<!-- LOCALIZATION NOTE (performanceUI.exportButton): This string is displayed
- on a button that opens a dialog to export a saved profile data file. -->
<!ENTITY performanceUI.exportButton "Save">
<!-- LOCALIZATION NOTE (performanceUI.clearButton): This string is displayed
- on a button that remvoes all the recordings. -->
<!ENTITY performanceUI.clearButton "Clear">
<!-- LOCALIZATION NOTE (performanceUI.toolbar.*): These strings are displayed
- in the toolbar on buttons that select which view is currently shown. -->
<!ENTITY performanceUI.toolbar.waterfall "Waterfall">
<!ENTITY performanceUI.toolbar.js-calltree "Call Tree">
<!ENTITY performanceUI.toolbar.memory-calltree "Allocations">
<!ENTITY performanceUI.toolbar.js-flamegraph "JS Flame Chart">
<!ENTITY performanceUI.toolbar.memory-flamegraph "Allocations Flame Chart">
<!-- LOCALIZATION NOTE (performanceUI.table.*): These strings are displayed
- in the call tree headers for a recording. -->
<!ENTITY performanceUI.table.totalDuration "Total Time">
<!ENTITY performanceUI.table.totalDuration.tooltip "The amount of time spent in this function and functions it calls.">
<!ENTITY performanceUI.table.selfDuration "Self Time">
<!ENTITY performanceUI.table.selfDuration.tooltip "The amount of time spent only within this function.">
<!ENTITY performanceUI.table.totalPercentage "Total Cost">
<!ENTITY performanceUI.table.totalPercentage.tooltip "The percentage of time spent in this function and functions it calls.">
<!ENTITY performanceUI.table.selfPercentage "Self Cost">
<!ENTITY performanceUI.table.selfPercentage.tooltip "The percentage of time spent only within this function.">
<!ENTITY performanceUI.table.samples "Samples">
<!ENTITY performanceUI.table.samples.tooltip "The number of times this function was on the stack when the profiler took a sample.">
<!ENTITY performanceUI.table.function "Function">
<!ENTITY performanceUI.table.function.tooltip "The name and source location of the sampled function.">
<!ENTITY performanceUI.table.totalAlloc "Total Sampled Allocations">
<!ENTITY performanceUI.table.totalAlloc.tooltip "The total number of Object allocations sampled at this location and in callees.">
<!ENTITY performanceUI.table.selfAlloc "Self Sampled Allocations">
<!ENTITY performanceUI.table.selfAlloc.tooltip "The number of Object allocations sampled at this location.">
<!-- LOCALIZATION NOTE (performanceUI.newtab.tooltiptext): The tooltiptext shown
- on the "+" (new tab) button for a profile when a selection is available. -->
<!ENTITY performanceUI.newtab.tooltiptext "Add new tab from selection">
<!-- LOCALIZATION NOTE (performanceUI.toolbar.filter.tooltiptext): This string
- is displayed next to the filter button-->
<!ENTITY performanceUI.options.filter.tooltiptext "Select what data to display in the timeline">
<!-- LOCALIZATION NOTE (performanceUI.options.tooltiptext): This is the tooltip
- for the options button. -->
<!ENTITY performanceUI.options.gear.tooltiptext "Configure performance preferences.">
<!-- LOCALIZATION NOTE (performanceUI.invertTree): This is the label shown next to
- a checkbox that inverts and un-inverts the profiler's call tree. -->
<!ENTITY performanceUI.invertTree "Invert Call Tree">
<!ENTITY performanceUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
<!-- LOCALIZATION NOTE (performanceUI.invertFlameGraph): This is the label shown next to
- a checkbox that inverts and un-inverts the profiler's flame graph. -->
<!ENTITY performanceUI.invertFlameGraph "Invert Flame Chart">
<!ENTITY performanceUI.invertFlameGraph.tooltiptext "Inverting the flame chart displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
<!-- LOCALIZATION NOTE (performanceUI.showPlatformData): This is the
- label for the checkbox that toggles whether or not Gecko platform data
- is displayed in the profiler. -->
<!ENTITY performanceUI.showPlatformData "Show Gecko Platform Data">
<!ENTITY performanceUI.showPlatformData.tooltiptext "Showing platform data enables the JavaScript Profiler reports to include Gecko platform symbols.">
<!-- LOCALIZATION NOTE (performanceUI.flattenTreeRecursion): This is the
- label for the checkbox that toggles the flattening of tree recursion in inspected
- functions in the profiler. -->
<!ENTITY performanceUI.flattenTreeRecursion "Flatten Tree Recursion">
<!ENTITY performanceUI.flattenTreeRecursion.tooltiptext "Flatten recursion when inspecting functions.">
<!-- LOCALIZATION NOTE (performanceUI.enableMemory): This string
- is displayed next to a checkbox determining whether or not memory
- measurements are enabled. -->
<!ENTITY performanceUI.enableMemory "Record Memory">
<!ENTITY performanceUI.enableMemory.tooltiptext "Record memory consumption while profiling.">
<!-- LOCALIZATION NOTE (performanceUI.enableAllocations): This string
- is displayed next to a checkbox determining whether or not allocation
- measurements are enabled. -->
<!ENTITY performanceUI.enableAllocations "Record Allocations">
<!ENTITY performanceUI.enableAllocations.tooltiptext "Record Object allocations while profiling.">
<!-- LOCALIZATION NOTE (performanceUI.enableFramerate): This string
- is displayed next to a checkbox determining whether or not framerate
- is recorded. -->
<!ENTITY performanceUI.enableFramerate "Record Framerate">
<!ENTITY performanceUI.enableFramerate.tooltiptext "Record framerate while profiling.">
<!-- LOCALIZATION NOTE (performanceUI.enableJITOptimizations): This string
- is displayed next to a checkbox determining whether or not JIT optimization data
- should be recorded. -->
<!ENTITY performanceUI.enableJITOptimizations "Record JIT Optimizations">
<!ENTITY performanceUI.enableJITOptimizations.tooltiptext "Record JIT optimization data sampled in each JavaScript frame.">
<!-- LOCALIZATION NOTE (performanceUI.JITOptimizationsTitle): This string
- is displayed as the title of the JIT Optimizations panel. -->
<!ENTITY performanceUI.JITOptimizationsTitle "JIT Optimizations">
<!-- LOCALIZATION NOTE (performanceUI.console.recordingNoticeStart/recordingNoticeEnd):
- This string is displayed when a recording is selected that started via console.profile.
- Wraps the command used to start, like "Currently recording via console.profile("label")" -->
<!ENTITY performanceUI.console.recordingNoticeStart "Currently recording via">
<!ENTITY performanceUI.console.recordingNoticeEnd "">
<!-- LOCALIZATION NOTE (performanceUI.console.stopCommandStart/stopCommandEnd):
- This string is displayed when a recording is selected that started via console.profile.
- Indicates how to stop the recording, wrapping the command, like
- "Stop recording by entering console.profileEnd("label") into the console." -->
<!ENTITY performanceUI.console.stopCommandStart "Stop recording by entering">
<!ENTITY performanceUI.console.stopCommandEnd "into the console.">

View File

@ -2,77 +2,83 @@
# 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/.
# LOCALIZATION NOTE These strings are used inside the Profiler
# which is available from the Web Developer sub-menu -> 'Profiler'.
# LOCALIZATION NOTE These strings are used inside the Performance Tools
# which is available from the Web Developer sub-menu -> 'Performance'.
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (profiler.label):
# LOCALIZATION NOTE (performance.label):
# This string is displayed in the title of the tab when the profiler is
# displayed inside the developer tools window and in the Developer Tools Menu.
profiler.label2=Performance
performance.label=Performance
# LOCALIZATION NOTE (profiler.panelLabel):
# LOCALIZATION NOTE (performance.panelLabel):
# This is used as the label for the toolbox panel.
profiler.panelLabel2=Performance Panel
performance.panelLabel=Performance Panel
# LOCALIZATION NOTE (profiler2.commandkey, profiler.accesskey)
# LOCALIZATION NOTE (performance.commandkey, performance.accesskey)
# Used for the menuitem in the tool menu
profiler.commandkey2=VK_F5
profiler.accesskey=P
performance.commandkey=VK_F5
performance.accesskey=P
# LOCALIZATION NOTE (profiler.tooltip3):
# LOCALIZATION NOTE (performance.tooltip):
# This string is displayed in the tooltip of the tab when the profiler is
# displayed inside the developer tools window.
# Keyboard shortcut for JS Profiler will be shown inside brackets.
profiler.tooltip3=JavaScript Profiler (%S)
# Keyboard shortcut for Performance Tools will be shown inside brackets.
performance.tooltip=Performance (%S)
# LOCALIZATION NOTE (noRecordingsText): The text to display in the
# recordings menu when there are no recorded profiles yet.
noRecordingsText=There are no profiles yet.
# LOCALIZATION NOTE (recordingsList.itemLabel):
# This string is displayed in the recordings list of the Profiler,
# This string is displayed in the recordings list of the Performance Tools,
# identifying a set of function calls.
recordingsList.itemLabel=Recording #%S
# LOCALIZATION NOTE (recordingsList.recordingLabel):
# This string is displayed in the recordings list of the Profiler,
# This string is displayed in the recordings list of the Performance Tools,
# for an item that has not finished recording.
recordingsList.recordingLabel=In progress…
# LOCALIZATION NOTE (recordingsList.loadingLabel):
# This string is displayed in the recordings list of the Performance Tools,
# for an item that is finished and is loading.
recordingsList.loadingLabel=Loading…
# LOCALIZATION NOTE (recordingsList.durationLabel):
# This string is displayed in the recordings list of the Profiler,
# This string is displayed in the recordings list of the Performance Tools,
# for an item that has finished recording.
recordingsList.durationLabel=%S ms
# LOCALIZATION NOTE (recordingsList.saveLabel):
# This string is displayed in the recordings list of the Profiler,
# This string is displayed in the recordings list of the Performance Tools,
# for saving an item to disk.
recordingsList.saveLabel=Save
# LOCALIZATION NOTE (profile.tab):
# This string is displayed in the profile view for a tab, after the
# recording has finished, as the recording 'start → stop' range in milliseconds.
profile.tab=%1$S ms → %2$S ms
# LOCALIZATION NOTE (graphs.fps):
# This string is displayed in the framerate graph of the Profiler,
# This string is displayed in the framerate graph of the Performance Tools,
# as the unit used to measure frames per second. This label should be kept
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
graphs.fps=fps
# LOCALIZATION NOTE (graphs.ms):
# This string is displayed in the flamegraph of the Profiler,
# This string is displayed in the flamegraph of the Performance Tools,
# as the unit used to measure time (in milliseconds). This label should be kept
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
graphs.ms=ms
# LOCALIZATION NOTE (graphs.memory):
# This string is displayed in the memory graph of the Performance tool,
# as the unit used to memory consumption. This label should be kept
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
graphs.memory=MB
# LOCALIZATION NOTE (category.*):
# These strings are displayed in the categories graph of the Profiler,
# These strings are displayed in the categories graph of the Performance Tools,
# as the legend for each block in every bar. These labels should be kept
# AS SHORT AS POSSIBLE so they don't obstruct important parts of the graph.
category.other=Gecko
@ -85,11 +91,11 @@ category.storage=Storage
category.events=Input & Events
category.tools=Tools
# LOCALIZATION NOTE (graphs.ms):
# LOCALIZATION NOTE (table.ms):
# This string is displayed in the call tree after units of time in milliseconds.
table.ms=ms
# LOCALIZATION NOTE (graphs.ms):
# LOCALIZATION NOTE (table.percentage):
# This string is displayed in the call tree after units representing percentages.
table.percentage=%
@ -116,10 +122,13 @@ table.zoom.tooltiptext=Inspect frame in new tab
# have optimization data
table.view-optimizations.tooltiptext=View optimizations in JIT View
# LOCALIZATION NOTE (recordingsList.importDialogTitle):
# This string is displayed as a title for importing a recoring from disk.
recordingsList.importDialogTitle=Import recording…
# LOCALIZATION NOTE (recordingsList.saveDialogTitle):
# This string is displayed as a title for saving a recording to disk.
recordingsList.saveDialogTitle=Save profile
recordingsList.saveDialogTitle=Save recording
# LOCALIZATION NOTE (recordingsList.saveDialogJSONFilter):
# This string is displayed as a filter for saving a recording to disk.
@ -133,25 +142,31 @@ recordingsList.saveDialogAllFilter=All Files
# This string is displayed in a tooltip when no JIT optimizations were detected.
jit.optimizationFailure=Optimization failed
# LOCALIZATION NOTE (jit.samples2):
# LOCALIZATION NOTE (jit.samples):
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is displayed for the unit representing the number of times a
# frame is sampled.
# "#1" represents the number of samples
# example: 30 samples
jit.samples2=#1 sample;#1 samples
jit.samples=#1 sample;#1 samples
# LOCALIZATION NOTE (jit.empty):
# This string is displayed when there are no JIT optimizations to display.
jit.empty=No JIT optimizations recorded for this frame.
# LOCALIZATION NOTE (consoleProfile.recordingNotice/stopCommand):
# These strings are displayed when a recording is in progress, that was started from the console.
# TODO REMOVE
consoleProfile.recordingNotice=Currently recording profile "%S".
# TODO REMOVE
consoleProfile.stopCommand=Stop profiling by typing \"console.profileEnd(\'%S\')\" into the console.
# LOCALIZATION NOTE (timeline.tick):
# This string is displayed in the timeline overview, for delimiting ticks
# by time, in milliseconds.
timeline.tick=%S ms
# LOCALIZATION NOTE (profiler.bufferStatus):
# This string is displayed illustrating how full the profiler's circular buffer is.
profiler.bufferStatus=Buffer capacity: %S%
# LOCALIZATION NOTE (timeline.records):
# This string is displayed in the timeline waterfall, as a title for the menu.
timeline.records=RECORDS
# LOCALIZATION NOTE (profiler.bufferFull):
# This string is displayed when recording, indicating how much of the
# buffer is currently be used.
# %S is the percentage of the buffer used -- there are two "%"s after to escape
# the % that is actually displayed.
# Example: "Buffer 54% full"
profiler.bufferFull=Buffer %S%% full

View File

@ -1,166 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- LOCALIZATION NOTE : FILE This file contains the Profiler strings -->
<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
- keep it in English, or another language commonly spoken among web developers.
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!-- LOCALIZATION NOTE (profilerUI.emptyNotice1/2): This is the label shown
- in the call list view when empty. -->
<!-- TODO remove -->
<!ENTITY profilerUI.emptyNotice1 "Click on the">
<!-- TODO remove -->
<!ENTITY profilerUI.emptyNotice2 "button to start recording JavaScript function calls.">
<!-- LOCALIZATION NOTE (profilerUI.stopNotice1/2): This is the label shown
- in the call list view while recording a profile. -->
<!-- TODO remove -->
<!ENTITY profilerUI.stopNotice1 "Click on the">
<!-- TODO remove -->
<!ENTITY profilerUI.stopNotice2 "button again to stop profiling.">
<!-- LOCALIZATION NOTE (profilerUI.startRecording/profilerUI.stopRecording): These are
- the labels shown on the main recording buttons to start/stop recording. -->
<!ENTITY profilerUI.startRecording "Start Recording Performance">
<!ENTITY profilerUI.stopRecording "Stop Recording Performance">
<!-- LOCALIZATION NOTE (profilerUI.bufferStatusTooltip): This string
- is displayed as the tooltip for the buffer capacity during a recording. -->
<!ENTITY profilerUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
<!-- LOCALIZATION NOTE (profilerUI.bufferStatusFull): This string
- is displayed when the profiler's circular buffer has started to overlap. -->
<!ENTITY profilerUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
<!-- LOCALIZATION NOTE (profilerUI.loadingNotice): This is the label shown
- in the call list view while loading a profile. -->
<!ENTITY profilerUI.loadingNotice "Loading…">
<!-- LOCALIZATION NOTE (profilerUI.recordButton): This string is displayed
- on a button that starts a new profile. -->
<!-- TODO remove -->
<!ENTITY profilerUI.recordButton.tooltip "Record JavaScript function calls.">
<!-- LOCALIZATION NOTE (profilerUI.recordButton2): This string is displayed
- on a button that starts a new profile. -->
<!ENTITY profilerUI.recordButton2.tooltip "Toggle the recording state of a performance recording.">
<!-- LOCALIZATION NOTE (profilerUI.importButton): This string is displayed
- on a button that opens a dialog to import a saved profile data file. -->
<!ENTITY profilerUI.importButton "Import…">
<!-- LOCALIZATION NOTE (profilerUI.exportButton): This string is displayed
- on a button that opens a dialog to export a saved profile data file. -->
<!ENTITY profilerUI.exportButton "Save">
<!-- LOCALIZATION NOTE (profilerUI.clearButton): This string is displayed
- on a button that remvoes all the recordings. -->
<!ENTITY profilerUI.clearButton "Clear">
<!-- LOCALIZATION NOTE (profilerUI.toolbar.*): These strings are displayed
- in the toolbar on buttons that select which view is currently shown. -->
<!ENTITY profilerUI.toolbar.waterfall "Timeline">
<!ENTITY profilerUI.toolbar.js-calltree "JavaScript">
<!ENTITY profilerUI.toolbar.memory-calltree1 "Allocations">
<!ENTITY profilerUI.toolbar.js-flamegraph "JS Flame Chart">
<!ENTITY profilerUI.toolbar.memory-flamegraph1 "Allocations Flame Chart">
<!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
- in the call tree headers for a recording. -->
<!ENTITY profilerUI.table.totalDuration2 "Total Time">
<!ENTITY profilerUI.table.totalDuration.tooltip "The amount of time spent in this function and functions it calls.">
<!ENTITY profilerUI.table.selfDuration2 "Self Time">
<!ENTITY profilerUI.table.selfDuration.tooltip "The amount of time spent only within this function.">
<!ENTITY profilerUI.table.totalPercentage "Total Cost">
<!ENTITY profilerUI.table.totalPercentage.tooltip "The percentage of time spent in this function and functions it calls.">
<!ENTITY profilerUI.table.selfPercentage "Self Cost">
<!ENTITY profilerUI.table.selfPercentage.tooltip "The percentage of time spent only within this function.">
<!ENTITY profilerUI.table.samples "Samples">
<!ENTITY profilerUI.table.samples.tooltip "The number of times this function was on the stack when the profiler took a sample.">
<!ENTITY profilerUI.table.function "Function">
<!ENTITY profilerUI.table.function.tooltip "The name and source location of the sampled function.">
<!ENTITY profilerUI.table.totalAlloc1 "Total Sampled Allocations">
<!ENTITY profilerUI.table.totalAlloc.tooltip "The total number of Object allocations sampled at this location and in callees.">
<!ENTITY profilerUI.table.selfAlloc1 "Self Sampled Allocations">
<!ENTITY profilerUI.table.selfAlloc.tooltip "The number of Object allocations sampled at this location.">
<!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown
- on the "+" (new tab) button for a profile when a selection is available. -->
<!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
<!-- LOCALIZATION NOTE (profilerUI.toolbar.filter.tooltiptext): This string
- is displayed next to the filter button-->
<!ENTITY profilerUI.options.filter.tooltiptext "Select what data to display in the timeline">
<!-- LOCALIZATION NOTE (profilerUI.options.tooltiptext): This is the tooltip
- for the options button. -->
<!ENTITY profilerUI.options.gear.tooltiptext "Configure performance preferences.">
<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
- a checkbox that inverts and un-inverts the profiler's call tree. -->
<!ENTITY profilerUI.invertTree "Invert Call Tree">
<!ENTITY profilerUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
<!-- LOCALIZATION NOTE (profilerUI.invertFlameGraph): This is the label shown next to
- a checkbox that inverts and un-inverts the profiler's flame graph. -->
<!ENTITY profilerUI.invertFlameGraph "Invert Flame Chart">
<!ENTITY profilerUI.invertFlameGraph.tooltiptext "Inverting the flame chart displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
<!-- LOCALIZATION NOTE (profilerUI.showPlatformData): This is the
- label for the checkbox that toggles whether or not Gecko platform data
- is displayed in the profiler. -->
<!ENTITY profilerUI.showPlatformData "Show Gecko Platform Data">
<!ENTITY profilerUI.showPlatformData.tooltiptext "Showing platform data enables the JavaScript Profiler reports to include Gecko platform symbols.">
<!-- LOCALIZATION NOTE (profilerUI.flattenTreeRecursion): This is the
- label for the checkbox that toggles the flattening of tree recursion in inspected
- functions in the profiler. -->
<!ENTITY profilerUI.flattenTreeRecursion "Flatten Tree Recursion">
<!ENTITY profilerUI.flattenTreeRecursion.tooltiptext "Flatten recursion when inspecting functions.">
<!-- LOCALIZATION NOTE (profilerUI.enableMemory): This string
- is displayed next to a checkbox determining whether or not memory
- measurements are enabled. -->
<!ENTITY profilerUI.enableMemory "Record Memory">
<!ENTITY profilerUI.enableMemory.tooltiptext "Record memory consumption while profiling.">
<!-- LOCALIZATION NOTE (profilerUI.enableAllocations): This string
- is displayed next to a checkbox determining whether or not allocation
- measurements are enabled. -->
<!ENTITY profilerUI.enableAllocations "Record Allocations">
<!ENTITY profilerUI.enableAllocations.tooltiptext "Record Object allocations while profiling.">
<!-- LOCALIZATION NOTE (profilerUI.enableFramerate): This string
- is displayed next to a checkbox determining whether or not framerate
- is recorded. -->
<!ENTITY profilerUI.enableFramerate "Record Framerate">
<!ENTITY profilerUI.enableFramerate.tooltiptext "Record framerate while profiling.">
<!-- LOCALIZATION NOTE (profilerUI.enableJITOptimizations): This string
- is displayed next to a checkbox determining whether or not JIT optimization data
- should be recorded. -->
<!ENTITY profilerUI.enableJITOptimizations "Record JIT Optimizations">
<!ENTITY profilerUI.enableJITOptimizations.tooltiptext "Record JIT optimization data sampled in each JavaScript frame.">
<!-- LOCALIZATION NOTE (profilerUI.JITOptimizationsTitle): This string
- is displayed as the title of the JIT Optimizations panel. -->
<!ENTITY profilerUI.JITOptimizationsTitle "JIT Optimizations">
<!-- LOCALIZATION NOTE (profilerUI.console.recordingNoticeStart/recordingNoticeEnd):
- This string is displayed when a recording is selected that started via console.profile.
- Wraps the command used to start, like "Currently recording via console.profile("label")" -->
<!ENTITY profilerUI.console.recordingNoticeStart "Currently recording via">
<!ENTITY profilerUI.console.recordingNoticeEnd "">
<!-- LOCALIZATION NOTE (profilerUI.console.stopCommandStart/stopCommandEnd):
- This string is displayed when a recording is selected that started via console.profile.
- Indicates how to stop the recording, wrapping the command, like
- "Stop recording by entering console.profilEnd("label") into the console." -->
<!ENTITY profilerUI.console.stopCommandStart "Stop recording by entering">
<!ENTITY profilerUI.console.stopCommandEnd "into the console.">

View File

@ -1,43 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- LOCALIZATION NOTE : FILE This file contains the Timeline strings -->
<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
- keep it in English, or another language commonly spoken among web developers.
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!-- LOCALIZATION NOTE (timelineUI.recordButton): This string is displayed
- on a button that starts a new recording. -->
<!ENTITY timelineUI.recordButton.tooltip "Record timeline operations">
<!-- LOCALIZATION NOTE (timelineUI.recordLabel): This string is displayed
- as a label to signal that a recording is in progress. -->
<!ENTITY timelineUI.recordLabel "Recording…">
<!-- LOCALIZATION NOTE (timelineUI.memoryCheckbox.label): This string
- is displayed next to a checkbox determining whether or not memory
- measurements are enabled. -->
<!ENTITY timelineUI.memoryCheckbox.label "Memory">
<!-- LOCALIZATION NOTE (timelineUI.memoryCheckbox.tooltip): This string
- is displayed next to the memory checkbox -->
<!ENTITY timelineUI.memoryCheckbox.tooltip "Enable memory measurements">
<!-- LOCALIZATION NOTE (timelineUI.filterButton.tooltip): This string
- is displayed next to the filter button-->
<!ENTITY timelineUI.filterButton.tooltip "Select what data to display">
<!-- LOCALIZATION NOTE (timelineUI.emptyNotice1/2): This is the label shown
- in the timeline view when empty. -->
<!ENTITY timelineUI.emptyNotice1 "Click on the">
<!ENTITY timelineUI.emptyNotice2 "button to start recording timeline events.">
<!-- LOCALIZATION NOTE (timelineUI.stopNotice1/2): This is the label shown
- in the timeline view while recording. -->
<!ENTITY timelineUI.stopNotice1 "Click on the">
<!ENTITY timelineUI.stopNotice2 "button again to stop recording.">

View File

@ -1,79 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# LOCALIZATION NOTE These strings are used inside the Timeline
# which is available from the Web Developer sub-menu -> 'Timeline'.
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (timeline.label):
# This string is displayed in the title of the tab when the timeline is
# displayed inside the developer tools window and in the Developer Tools Menu.
timeline.label=Timeline
# LOCALIZATION NOTE (timeline.panelLabel):
# This is used as the label for the toolbox panel.
timeline.panelLabel=Timeline Panel
# LOCALIZATION NOTE (timeline.tooltip):
# This string is displayed in the tooltip of the tab when the timeline is
# displayed inside the developer tools window.
timeline.tooltip=Performance Timeline
# LOCALIZATION NOTE (timeline.tick):
# This string is displayed in the timeline overview, for delimiting ticks
# by time, in milliseconds.
timeline.tick=%S ms
# LOCALIZATION NOTE (timeline.records):
# This string is displayed in the timeline waterfall, as a title for the menu.
timeline.records=RECORDS
# LOCALIZATION NOTE (timeline.label.*):
# These strings are displayed in the timeline waterfall, identifying markers.
# We want to use the same wording as Google Chrome
timeline.label.styles2=Recalculate Style
timeline.label.reflow2=Layout
timeline.label.paint=Paint
timeline.label.javascript2=Function Call
timeline.label.parseHTML=Parse HTML
timeline.label.parseXML=Parse XML
timeline.label.domevent=DOM Event
timeline.label.consoleTime=Console
timeline.label.garbageCollection=GC Event
timeline.label.timestamp=Timestamp
timeline.label.unknown=Unknown
# LOCALIZATION NOTE (graphs.memory):
# This string is displayed in the memory graph of the Performance tool,
# as the unit used to memory consumption. This label should be kept
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
graphs.memory=MB
# LOCALIZATION NOTE (timeline.markerDetailFormat):
# Some timeline markers come with details, like a size, a name, a js function.
# %1$S is replaced with one of the above label (timeline.label.*) and %2$S
# with the details. For examples: Paint (200x100), or console.time (FOO)
timeline.markerDetailFormat=%1$S (%2$S)
# LOCALIZATION NOTE (time.markerDetail.*):
# Strings used in the waterfall sidebar.
timeline.markerDetail.start=Start:
timeline.markerDetail.end=End:
timeline.markerDetail.duration=Duration:
timeline.markerDetail.consoleTimerName=Timer Name:
timeline.markerDetail.DOMEventType=Event Type:
timeline.markerDetail.DOMEventPhase=Phase:
timeline.markerDetail.DOMEventTargetPhase=Target
timeline.markerDetail.DOMEventCapturingPhase=Capture
timeline.markerDetail.DOMEventBubblingPhase=Bubbling
timeline.markerDetail.stack=Stack:
timeline.markerDetail.startStack=Stack at start:
timeline.markerDetail.endStack=Stack at end:
timeline.markerDetail.unknownFrame=<unknown location>
timeline.markerDetail.asyncStack=(Async: %S)
timeline.markerDetail.causeName=Cause:

View File

@ -117,6 +117,9 @@ valid_email_text_description=Please enter a valid email address
## panel.
add_or_import_contact_title=Add or Import Contact
import_contacts_button2=Import from Google
## LOCALIZATION NOTE (import_contacts_button3): Text for button used to import
## contacts into the contact list.
import_contacts_button3=Import
importing_contacts_progress_button=Importing…
import_contacts_failure_message=Some contacts could not be imported. Please try again.
## LOCALIZATION NOTE(import_contacts_success_message): Success notification message

View File

@ -59,17 +59,16 @@
locale/browser/devtools/VariablesView.dtd (%chrome/browser/devtools/VariablesView.dtd)
locale/browser/devtools/sourceeditor.properties (%chrome/browser/devtools/sourceeditor.properties)
locale/browser/devtools/sourceeditor.dtd (%chrome/browser/devtools/sourceeditor.dtd)
locale/browser/devtools/profiler.dtd (%chrome/browser/devtools/profiler.dtd)
locale/browser/devtools/profiler.properties (%chrome/browser/devtools/profiler.properties)
locale/browser/devtools/promisedebugger.dtd (%chrome/browser/devtools/promisedebugger.dtd)
locale/browser/devtools/promisedebugger.properties (%chrome/browser/devtools/promisedebugger.properties)
locale/browser/devtools/performance.dtd (%chrome/browser/devtools/performance.dtd)
locale/browser/devtools/performance.properties (%chrome/browser/devtools/performance.properties)
locale/browser/devtools/layoutview.dtd (%chrome/browser/devtools/layoutview.dtd)
locale/browser/devtools/responsiveUI.properties (%chrome/browser/devtools/responsiveUI.properties)
locale/browser/devtools/toolbox.dtd (%chrome/browser/devtools/toolbox.dtd)
locale/browser/devtools/toolbox.properties (%chrome/browser/devtools/toolbox.properties)
locale/browser/devtools/inspector.dtd (%chrome/browser/devtools/inspector.dtd)
locale/browser/devtools/timeline.dtd (%chrome/browser/devtools/timeline.dtd)
locale/browser/devtools/timeline.properties (%chrome/browser/devtools/timeline.properties)
locale/browser/devtools/markers.properties (%chrome/browser/devtools/markers.properties)
locale/browser/devtools/projecteditor.properties (%chrome/browser/devtools/projecteditor.properties)
locale/browser/devtools/eyedropper.properties (%chrome/browser/devtools/eyedropper.properties)
locale/browser/devtools/connection-screen.dtd (%chrome/browser/devtools/connection-screen.dtd)

View File

@ -789,7 +789,7 @@ public class AndroidFxAccount {
updateBundleValues(BUNDLE_KEY_PROFILE_JSON, resultData);
Logger.info(LOG_TAG, "Profile JSON fetch succeeeded!");
FxAccountUtils.pii(LOG_TAG, "Profile JSON fetch returned: " + resultData);
LocalBroadcastManager.getInstance(context).sendBroadcast(makeDeletedAccountIntent());
LocalBroadcastManager.getInstance(context).sendBroadcast(makeProfileJSONUpdatedIntent());
break;
case Activity.RESULT_CANCELED:
Logger.warn(LOG_TAG, "Failed to fetch profile JSON; ignoring.");

View File

@ -51,7 +51,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;

View File

@ -0,0 +1,122 @@
package org.mozilla.gecko.home;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.mozilla.gecko.R;
import java.util.Collections;
import java.util.List;
public class SearchEngineAdapter
extends RecyclerView.Adapter<SearchEngineAdapter.SearchEngineViewHolder> {
private static final String LOGTAG = SearchEngineAdapter.class.getSimpleName();
private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
private static final int VIEW_TYPE_LABEL = 1;
private final Context mContext;
private int mContainerWidth;
private List<SearchEngine> mSearchEngines = Collections.emptyList();
public void setSearchEngines(List<SearchEngine> searchEngines) {
mSearchEngines = searchEngines;
notifyDataSetChanged();
}
/**
* The container width is used for setting the appropriate calculated amount of width that
* a search engine icon can have. This varies depending on the space available in the
* {@link SearchEngineBar}. The setter exists for this attribute, in creating the view in the
* adapter after said calculation is done when the search bar is created.
* @param iconContainerWidth Width of each search icon.
*/
void setIconContainerWidth(int iconContainerWidth) {
mContainerWidth = iconContainerWidth;
}
public static class SearchEngineViewHolder extends RecyclerView.ViewHolder {
final private ImageView faviconView;
public void bindItem(SearchEngine searchEngine) {
faviconView.setImageBitmap(searchEngine.getIcon());
final String desc = itemView.getResources().getString(R.string.search_bar_item_desc,
searchEngine.getEngineIdentifier());
itemView.setContentDescription(desc);
}
public SearchEngineViewHolder(View itemView) {
super(itemView);
faviconView = (ImageView) itemView.findViewById(R.id.search_engine_icon);
}
}
public SearchEngineAdapter(Context context) {
mContext = context;
}
@Override
public int getItemViewType(int position) {
return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
}
public SearchEngine getItem(int position) {
// We omit the first position which is where the label currently is.
return position == 0 ? null : mSearchEngines.get(position - 1);
}
@Override
public SearchEngineViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_LABEL:
return new SearchEngineViewHolder(createLabelView(parent));
case VIEW_TYPE_SEARCH_ENGINE:
return new SearchEngineViewHolder(createSearchEngineView(parent));
default:
throw new IllegalArgumentException("Unknown view type: " + viewType);
}
}
@Override
public void onBindViewHolder(SearchEngineViewHolder holder, int position) {
if (position != 0) {
holder.bindItem(getItem(position));
}
}
@Override
public int getItemCount() {
return mSearchEngines.size() + 1;
}
private View createLabelView(ViewGroup parent) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.search_engine_bar_label, parent, false);
final Drawable icon = DrawableCompat.wrap(
ContextCompat.getDrawable(mContext, R.drawable.search_icon_active).mutate());
DrawableCompat.setTint(icon, mContext.getResources().getColor(R.color.disabled_grey));
final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
iconView.setImageDrawable(icon);
return view;
}
private View createSearchEngineView(ViewGroup parent) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.search_engine_bar_item, parent, false);
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = mContainerWidth;
view.setLayoutParams(params);
return view;
}
}

View File

@ -3,213 +3,144 @@
* 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/. */
package org.mozilla.gecko.home;
package org.mozilla.gecko.home;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.widget.TwoWayView;
import org.mozilla.gecko.mozglue.RobocopTarget;
import java.util.ArrayList;
import java.util.List;
public class SearchEngineBar extends TwoWayView
implements AdapterView.OnItemClickListener {
private static final String LOGTAG = "Gecko" + SearchEngineBar.class.getSimpleName();
public class SearchEngineBar extends RecyclerView
implements RecyclerViewItemClickListener.OnClickListener {
private static final String LOGTAG = SearchEngineBar.class.getSimpleName();
private static final float ICON_CONTAINER_MIN_WIDTH_DP = 72;
private static final float LABEL_CONTAINER_WIDTH_DP = 48;
private static final float DIVIDER_HEIGHT_DP = 1;
public interface OnSearchBarClickListener {
public void onSearchBarClickListener(SearchEngine searchEngine);
void onSearchBarClickListener(SearchEngine searchEngine);
}
private final SearchEngineAdapter adapter;
private final Paint dividerPaint;
private final float minIconContainerWidth;
private final float dividerHeight;
private final int labelContainerWidth;
private final SearchEngineAdapter mAdapter;
private final LinearLayoutManager mLayoutManager;
private final Paint mDividerPaint;
private final float mMinIconContainerWidth;
private final float mDividerHeight;
private final int mLabelContainerWidth;
private int iconContainerWidth;
private OnSearchBarClickListener onSearchBarClickListener;
private int mIconContainerWidth;
private OnSearchBarClickListener mOnSearchBarClickListener;
public SearchEngineBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
dividerPaint = new Paint();
dividerPaint.setColor(getResources().getColor(R.color.divider_light));
dividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mDividerPaint = new Paint();
mDividerPaint.setColor(getResources().getColor(R.color.divider_light));
mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
minIconContainerWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
dividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIVIDER_HEIGHT_DP, displayMetrics);
labelContainerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics);
mMinIconContainerWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
mDividerHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, DIVIDER_HEIGHT_DP, displayMetrics);
mLabelContainerWidth = Math.round(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics));
iconContainerWidth = (int) minIconContainerWidth;
mIconContainerWidth = Math.round(mMinIconContainerWidth);
adapter = new SearchEngineAdapter();
setAdapter(adapter);
setOnItemClickListener(this);
mAdapter = new SearchEngineAdapter(context);
mAdapter.setIconContainerWidth(mIconContainerWidth);
mLayoutManager = new LinearLayoutManager(context);
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
setAdapter(mAdapter);
setLayoutManager(mLayoutManager);
addOnItemTouchListener(new RecyclerViewItemClickListener(context, this, this));
}
@Override
public void onItemClick(final AdapterView<?> parent, final View view, final int position,
final long id) {
if (onSearchBarClickListener == null) {
throw new IllegalStateException(
OnSearchBarClickListener.class.getSimpleName() + " is not initialized");
}
if (position == 0) {
// Ignore click on label
return;
}
final SearchEngine searchEngine = adapter.getItem(position);
onSearchBarClickListener.onSearchBarClickListener(searchEngine);
public void setSearchEngines(List<SearchEngine> searchEngines) {
mAdapter.setSearchEngines(searchEngines);
}
protected void setOnSearchBarClickListener(final OnSearchBarClickListener listener) {
onSearchBarClickListener = listener;
}
protected void setSearchEngines(final List<SearchEngine> searchEngines) {
adapter.setSearchEngines(searchEngines);
public void setOnSearchBarClickListener(OnSearchBarClickListener listener) {
mOnSearchBarClickListener = listener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int searchEngineCount = adapter.getCount() - 1;
final int searchEngineCount = mAdapter.getItemCount() - 1;
if (searchEngineCount > 0) {
final int availableWidth = getMeasuredWidth() - labelContainerWidth;
final int availableWidth = getMeasuredWidth() - mLabelContainerWidth;
final double searchEnginesToDisplay;
if (searchEngineCount * minIconContainerWidth <= availableWidth) {
if (searchEngineCount * mMinIconContainerWidth <= availableWidth) {
// All search engines fit int: So let's just display all.
searchEnginesToDisplay = searchEngineCount;
} else {
// If only (n) search engines fit into the available space then display (n - 0.5): The last search
// engine will be cut-off to show ability to scroll this view
searchEnginesToDisplay = Math.floor(availableWidth / minIconContainerWidth) - 0.5;
searchEnginesToDisplay = Math.floor(availableWidth / mMinIconContainerWidth) - 0.5;
}
// Use all available width and spread search engine icons
final int availableWidthPerContainer = (int) (availableWidth / searchEnginesToDisplay);
if (availableWidthPerContainer != iconContainerWidth) {
iconContainerWidth = availableWidthPerContainer;
adapter.notifyDataSetChanged();
if (availableWidthPerContainer != mIconContainerWidth) {
mIconContainerWidth = availableWidthPerContainer;
}
mAdapter.setIconContainerWidth(mIconContainerWidth);
}
}
@Override
protected void onDraw(Canvas canvas) {
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), dividerHeight, dividerPaint);
canvas.drawRect(0, 0, getWidth(), mDividerHeight, mDividerPaint);
}
public class SearchEngineAdapter extends BaseAdapter {
private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
private static final int VIEW_TYPE_LABEL = 1;
List<SearchEngine> searchEngines = new ArrayList<>();
public void setSearchEngines(final List<SearchEngine> searchEngines) {
this.searchEngines = searchEngines;
notifyDataSetChanged();
@Override
public void onClick(View view, int position) {
if (mOnSearchBarClickListener == null) {
throw new IllegalStateException(
OnSearchBarClickListener.class.getSimpleName() + " is not initializer."
);
}
@Override
public int getCount() {
// Adding offset for label at position 0 (Bug 1172071)
return searchEngines.size() + 1;
if (position == 0) {
return;
}
@Override
public SearchEngine getItem(final int position) {
// Returning null for the label at position 0 (Bug 1172071)
return position == 0 ? null : searchEngines.get(position - 1);
}
final SearchEngine searchEngine = mAdapter.getItem(position);
mOnSearchBarClickListener.onSearchBarClickListener(searchEngine);
}
@Override
public long getItemId(final int position) {
return position;
}
@Override
public void onLongClick(View view, int position) {
// do nothing
}
@Override
public int getItemViewType(int position) {
return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
if (position == 0) {
return getLabelView(convertView, parent);
} else {
return getSearchEngineView(position, convertView, parent);
}
}
private View getLabelView(View view, final ViewGroup parent) {
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.search_engine_bar_label, parent, false);
}
final Drawable icon =
DrawableUtil.tintDrawable(parent.getContext(), R.drawable.search_icon_active, R.color.disabled_grey);
final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
iconView.setImageDrawable(icon);
iconView.setScaleType(ImageView.ScaleType.FIT_XY);
return view;
}
private View getSearchEngineView(final int position, View view, final ViewGroup parent) {
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.search_engine_bar_item, parent, false);
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
params.width = iconContainerWidth;
view.setLayoutParams(params);
final ImageView faviconView = (ImageView) view.findViewById(R.id.search_engine_icon);
final SearchEngine searchEngine = getItem(position);
faviconView.setImageBitmap(searchEngine.getIcon());
final String desc = getResources().getString(R.string.search_bar_item_desc, searchEngine.getEngineIdentifier());
view.setContentDescription(desc);
return view;
}
/**
* We manually add the override for getAdapter because we see this method getting stripped
* out during compile time by aggressive proguard rules.
*/
@RobocopTarget
@Override
public SearchEngineAdapter getAdapter() {
return mAdapter;
}
}

View File

@ -352,6 +352,7 @@ gbjar.sources += [
'home/RemoteTabsSplitPlaneFragment.java',
'home/RemoteTabsStaticFragment.java',
'home/SearchEngine.java',
'home/SearchEngineAdapter.java',
'home/SearchEngineBar.java',
'home/SearchEngineRow.java',
'home/SearchLoader.java',

View File

@ -17,6 +17,7 @@
android:id="@+id/search_engine_icon_container"
android:layout_width="72dp"
android:layout_height="match_parent"
android:clickable="true"
android:background="@color/pressed_about_page_header_grey">
<!-- Width & height are set to make the Favicons as sharp as possible

View File

@ -16,6 +16,6 @@
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
android:scaleType="fitCenter"/>
android:scaleType="fitXY"/>
</FrameLayout>

View File

@ -4674,12 +4674,13 @@ Tab.prototype = {
this.pluginDoorhangerTimeout = null;
this.shouldShowPluginDoorhanger = true;
this.clickToPlayPluginsActivated = false;
// Borrowed from desktop Firefox: http://mxr.mozilla.org/mozilla-central/source/browser/base/content/urlbarBindings.xml#174
let documentURI = contentWin.document.documentURIObject.spec
let documentURI = contentWin.document.documentURIObject.spec;
// If reader mode, get the base domain for the original url.
let strippedURI = this._stripAboutReaderURL(documentURI);
// Borrowed from desktop Firefox: http://hg.mozilla.org/mozilla-central/annotate/72835344333f/browser/base/content/urlbarBindings.xml#l236
let matchedURL = strippedURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
let baseDomain = "";
if (matchedURL) {
@ -4696,13 +4697,24 @@ Tab.prototype = {
} catch (e) {}
}
// If we are navigating to a new location with a different host,
// clear any URL origin that might have been pinned to this tab.
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
let appOrigin = ss.getTabValue(this, "appOrigin");
if (appOrigin) {
let originHost = Services.io.newURI(appOrigin, null, null).host;
if (originHost != aLocationURI.host) {
// Note: going 'back' will not make this tab pinned again
ss.deleteTabValue(this, "appOrigin");
}
}
// Update the page actions URI for helper apps.
if (BrowserApp.selectedTab == this) {
ExternalApps.updatePageActionUri(fixedURI);
}
let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
let message = {
type: "Content:LocationChange",

View File

@ -1196,19 +1196,19 @@ SessionStore.prototype = {
setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
let browser = aTab.browser;
if (!browser.__SS_extdata)
if (!browser.__SS_extdata) {
browser.__SS_extdata = {};
}
browser.__SS_extdata[aKey] = aStringValue;
this.saveStateDelayed();
},
deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
let browser = aTab.browser;
if (browser.__SS_extdata && browser.__SS_extdata[aKey])
if (browser.__SS_extdata && aKey in browser.__SS_extdata) {
delete browser.__SS_extdata[aKey];
else
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
this.saveStateDelayed();
}
},
restoreLastSession: Task.async(function* (aSessionString) {

View File

@ -30,9 +30,23 @@ android {
java {
exclude 'org/mozilla/gecko/tests/**'
exclude 'org/mozilla/gecko/resources/**'
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
exclude 'org/mozilla/gecko/CrashReporter.java'
}
if (!mozconfig.substs.MOZ_NATIVE_DEVICES) {
exclude 'org/mozilla/gecko/ChromeCast.java'
exclude 'org/mozilla/gecko/GeckoMediaPlayer.java'
exclude 'org/mozilla/gecko/MediaPlayerManager.java'
}
if (mozconfig.substs.MOZ_WEBRTC) {
srcDir 'src/webrtc_audio_device'
srcDir 'src/webrtc_video_capture'
srcDir 'src/webrtc_video_render'
}
// Adjust helpers are included in the preprocessed_code project.
exclude 'org/mozilla/gecko/adjust/**'
}

View File

@ -138,6 +138,9 @@ class MachCommands(MachCommandBase):
srcdir('base/src/main/java/org/mozilla/mozstumbler', 'mobile/android/stumbler/java/org/mozilla/mozstumbler')
srcdir('base/src/main/java/org/mozilla/search', 'mobile/android/search/java/org/mozilla/search')
srcdir('base/src/main/java/org/mozilla/javaaddons', 'mobile/android/javaaddons/java/org/mozilla/javaaddons')
srcdir('base/src/webrtc_audio_device/java', 'media/webrtc/trunk/webrtc/modules/audio_device/android/java/src')
srcdir('base/src/webrtc_video_capture/java', 'media/webrtc/trunk/webrtc/modules/video_capture/android/java/src')
srcdir('base/src/webrtc_video_render/java', 'media/webrtc/trunk/webrtc/modules/video_render/android/java/src')
srcdir('base/src/main/res', 'mobile/android/base/resources')
srcdir('base/src/crashreporter/res', 'mobile/android/base/crashreporter/res')

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.Actions;
import org.mozilla.gecko.home.HomePager;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@ -208,7 +209,7 @@ abstract class AboutHomeTest extends PixelTest {
/**
* Swipes to an about:home tab.
* @param int swipeVector Value and direction to swipe (go left for negative, right for positive).
* @param swipeVector swipeVector Value and direction to swipe (go left for negative, right for positive).
*/
private void swipeAboutHome(int swipeVector) {
// Increase swipe width, which will especially impact tablets.
@ -232,8 +233,6 @@ abstract class AboutHomeTest extends PixelTest {
/**
* This method can be used to open the different tabs of about:home.
*
* @param AboutHomeTabs enum item
*/
protected void openAboutHomeTab(AboutHomeTabs tab) {
focusUrlBar();

View File

@ -157,7 +157,7 @@ public class testAddSearchEngine extends AboutHomeTest {
}
final int actualCount = searchResultList.getAdapter().getCount()
+ searchEngineBar.getAdapter().getCount()
+ searchEngineBar.getAdapter().getItemCount()
- 1; // Subtract one for the search engine bar label (Bug 1172071)
return (actualCount == expectedCount);

View File

@ -92,14 +92,14 @@ def _generate_geckoview_classes_jar(distdir, base_path):
_zipdir(geckoview_aar_classes_path, classes_jar_path)
return File(classes_jar_path)
def package_geckolibs_aar(topsrcdir, distdir, output_file):
def package_geckolibs_aar(topsrcdir, distdir, appname, output_file):
jarrer = Jarrer(optimize=False)
srcdir = os.path.join(topsrcdir, 'mobile', 'android', 'geckoview_library', 'geckolibs')
jarrer.add('AndroidManifest.xml', File(os.path.join(srcdir, 'AndroidManifest.xml')))
jarrer.add('classes.jar', File(os.path.join(srcdir, 'classes.jar')))
jni = FileFinder(os.path.join(distdir, 'fennec', 'lib'))
jni = FileFinder(os.path.join(distdir, appname, 'lib'))
for p, f in jni.find('**/*.so'):
jarrer.add(os.path.join('jni', p), f)
@ -110,17 +110,17 @@ def package_geckolibs_aar(topsrcdir, distdir, output_file):
jarrer.add(os.path.join('assets', p), f)
# This neatly ignores omni.ja.
assets = FileFinder(os.path.join(distdir, 'fennec', 'assets'))
assets = FileFinder(os.path.join(distdir, appname, 'assets'))
for p, f in assets.find('**/*.so'):
jarrer.add(os.path.join('assets', p), f)
jarrer.copy(output_file)
return 0
def package_geckoview_aar(topsrcdir, distdir, output_file):
def package_geckoview_aar(topsrcdir, distdir, appname, output_file):
jarrer = Jarrer(optimize=False)
fennec_path = os.path.join(distdir, 'fennec')
assets = FileFinder(os.path.join(fennec_path, 'assets'), ignore=['*.so'])
app_path = os.path.join(distdir, appname)
assets = FileFinder(os.path.join(app_path, 'assets'), ignore=['*.so'])
for p, f in assets.find('omni.ja'):
jarrer.add(os.path.join('assets', p), f)
@ -129,7 +129,7 @@ def package_geckoview_aar(topsrcdir, distdir, output_file):
# The resource set is packaged during Fennec's build.
resjar = JarReader(os.path.join(base_path, 'geckoview_resources.zip'))
for p, f in JarFinder(p, resjar).find('*'):
for p, f in JarFinder(base_path, resjar).find('*'):
jarrer.add(os.path.join('res', p), f)
# Package the contents of all Fennec JAR files into classes.jar.
@ -159,6 +159,8 @@ def main(args):
help='Top source directory.')
parser.add_argument('--distdir',
help='Distribution directory (usually $OBJDIR/dist).')
parser.add_argument('--appname',
help='Application name (usually $MOZ_APP_NAME, like "fennec").')
args = parser.parse_args(args)
# An Ivy 'publication' date must be given in the form yyyyMMddHHmmss, and Mozilla buildids are in this format.
@ -174,8 +176,8 @@ def main(args):
geckoview_aar = os.path.join(args.dir, 'geckoview-{revision}.aar').format(revision=args.revision)
paths_to_hash.append(geckoview_aar)
package_geckolibs_aar(args.topsrcdir, args.distdir, gecklibs_aar)
package_geckoview_aar(args.topsrcdir, args.distdir, geckoview_aar)
package_geckolibs_aar(args.topsrcdir, args.distdir, args.appname, gecklibs_aar)
package_geckoview_aar(args.topsrcdir, args.distdir, args.appname, geckoview_aar)
geckolibs_pom_path = os.path.join(args.dir, 'geckolibs-{revision}.pom').format(revision=args.revision)
paths_to_hash.append(geckolibs_pom_path)

View File

@ -206,9 +206,7 @@ class TaskCache(CacheManager):
'Unknown job {job}')
raise KeyError("Unknown job")
# Bug 1175655: it appears that the Task Cluster index only takes
# 12-char hex hashes.
key = '{rev}.{tree}.{job}'.format(rev=rev[:12], tree=tree, job=job)
key = '{rev}.{tree}.{job}'.format(rev=rev, tree=tree, job=job)
try:
namespace = 'buildbot.revisions.{key}'.format(key=key)
task = self._index.findTask(namespace)
@ -355,6 +353,9 @@ class Artifacts(object):
with self._task_cache as task_cache, self._pushhead_cache as pushhead_cache:
# with blocks handle handle persistence.
for pushhead in pushhead_cache.pushheads(self._tree, revset):
self.log(logging.DEBUG, 'artifact',
{'pushhead': pushhead},
'Trying to find artifacts for pushhead {pushhead}.')
try:
url = task_cache.artifact_url(self._tree, self._job, pushhead)
break
@ -362,6 +363,9 @@ class Artifacts(object):
pass
if url:
return self.install_from_url(url, distdir)
self.log(logging.ERROR, 'artifact',
{'revset': revset},
'No built artifacts for {revset} found.')
return 1
def install_from(self, source, distdir):

View File

@ -1070,7 +1070,7 @@ function UserAutoCompleteResult (aSearchString, matchingLogins) {
if (userA < userB)
return -1;
if (userB > userA)
if (userA > userB)
return 1;
return 0;

View File

@ -7876,7 +7876,7 @@
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 4,
"description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked, 3 = due to mixed content"
"description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked)"
},
"TRACKING_PROTECTION_EVENTS": {
"expires_in_version": "never",

View File

@ -123,6 +123,7 @@
<li><a href="about:license#praton">praton License</a></li>
<li><a href="about:license#qcms">qcms License</a></li>
<li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
<li><a href="about:license#react">React License</a></li>
<li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
<li><a href="about:license#hunspell-ru">Russian Spellchecking Dictionary License</a></li>
<li><a href="about:license#sctp">SCTP Licenses</a></li>
@ -3742,6 +3743,42 @@ THE SOFTWARE.
</pre>
<hr>
<h1><a id="react"></a>React License</h1>
<p>This license applies to various files in the Mozilla codebase.</p>
<pre>
Copyright (c) 2013-2015, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>
<hr>
<h1><a id="xdg"></a>Red Hat xdg_user_dir_lookup License</h1>

View File

@ -729,10 +729,13 @@
<property name="_selected">
<setter><![CDATA[
if (val)
if (val) {
this.setAttribute("selected", "true");
else
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("selected");
this.removeAttribute("visuallyselected");
}
this._setPositionAttributes(val);

View File

@ -2292,6 +2292,37 @@ SourceActor.prototype = {
}
},
_reportLoadSourceError: function (error, map=null) {
try {
DevToolsUtils.reportException("SourceActor", error);
JSON.stringify(this.form(), null, 4).split(/\n/g)
.forEach(line => console.error("\t", line));
if (!map) {
return;
}
console.error("\t", "source map's sourceRoot =", map.sourceRoot);
console.error("\t", "source map's sources =");
map.sources.forEach(s => {
let hasSourceContent = map.sourceContentFor(s, true);
console.error("\t\t", s, "\t",
hasSourceContent ? "has source content" : "no source content");
});
console.error("\t", "source map's sourcesContent =");
map.sourcesContent.forEach(c => {
if (c.length > 80) {
c = c.slice(0, 77) + "...";
}
c = c.replace(/\n/g, "\\n");
console.error("\t\t", c);
});
} catch (e) { }
},
_getSourceText: function () {
let toResolvedContent = t => ({
content: t,
@ -2300,9 +2331,16 @@ SourceActor.prototype = {
let genSource = this.generatedSource || this.source;
return this.threadActor.sources.fetchSourceMap(genSource).then(map => {
let sc;
if (map && (sc = map.sourceContentFor(this.url))) {
return toResolvedContent(sc);
if (map) {
try {
let sourceContent = map.sourceContentFor(this.url);
if (sourceContent) {
return toResolvedContent(sourceContent);
}
} catch (error) {
this._reportLoadSourceError(error, map);
throw error;
}
}
// Use `source.text` if it exists, is not the "no source"
@ -2327,10 +2365,14 @@ SourceActor.prototype = {
let sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource });
// Record the contentType we just learned during fetching
return sourceFetched.then(result => {
this._contentType = result.contentType;
return result;
});
return sourceFetched
.then(result => {
this._contentType = result.contentType;
return result;
}, error => {
this._reportLoadSourceError(error, map);
throw error;
});
}
});
},

View File

@ -95,7 +95,7 @@ let Memory = exports.Memory = Class({
_clearDebuggees: function() {
if (this._dbg) {
if (this.dbg.memory.trackingAllocationSites) {
if (this.isRecordingAllocations()) {
this.dbg.memory.drainAllocationsLog();
}
this._clearFrames();
@ -104,7 +104,7 @@ let Memory = exports.Memory = Class({
},
_clearFrames: function() {
if (this.dbg.memory.trackingAllocationSites) {
if (this.isRecordingAllocations()) {
this._frameCache.clearFrames();
}
},
@ -114,7 +114,7 @@ let Memory = exports.Memory = Class({
*/
_onWindowReady: function({ isTopLevel }) {
if (this.state == "attached") {
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
if (isTopLevel && this.isRecordingAllocations()) {
this._clearDebuggees();
this._frameCache.initFrames();
}
@ -122,6 +122,14 @@ let Memory = exports.Memory = Class({
}
},
/**
* Returns a boolean indicating whether or not allocation
* sites are being tracked.
*/
isRecordingAllocations: function () {
return this.dbg.memory.trackingAllocationSites;
},
/**
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
* more information.
@ -146,8 +154,8 @@ let Memory = exports.Memory = Class({
* resetting the timer.
*/
startRecordingAllocations: expectState("attached", function(options = {}) {
if (this.dbg.memory.trackingAllocationSites) {
return Date.now();
if (this.isRecordingAllocations()) {
return this._getCurrentTime();
}
this._frameCache.initFrames();
@ -171,13 +179,16 @@ let Memory = exports.Memory = Class({
}
this.dbg.memory.trackingAllocationSites = true;
return Date.now();
return this._getCurrentTime();
}, `starting recording allocations`),
/**
* Stop recording allocation sites.
*/
stopRecordingAllocations: expectState("attached", function() {
if (!this.isRecordingAllocations()) {
return this._getCurrentTime();
}
this.dbg.memory.trackingAllocationSites = false;
this._clearFrames();
@ -186,7 +197,7 @@ let Memory = exports.Memory = Class({
this._poller = null;
}
return Date.now();
return this._getCurrentTime();
}, `stopping recording allocations`),
/**
@ -380,4 +391,12 @@ let Memory = exports.Memory = Class({
events.emit(this, "allocations", this.getAllocations());
this._poller.arm();
},
/**
* Accesses the docshell to return the current process time.
*/
_getCurrentTime: function () {
return (this.parent.isRootActor ? this.parent.docShell : this.parent.originalDocShell).now();
},
});

View File

@ -2513,13 +2513,13 @@ this.XPIProvider = {
continue;
let signedState = yield verifyBundleSignedState(addon._sourceBundle, addon);
if (signedState == addon.signedState)
continue;
addon.signedState = signedState;
AddonManagerPrivate.callAddonListeners("onPropertyChanged",
createWrapper(addon),
["signedState"]);
if (signedState != addon.signedState) {
addon.signedState = signedState;
AddonManagerPrivate.callAddonListeners("onPropertyChanged",
createWrapper(addon),
["signedState"]);
}
let disabled = XPIProvider.updateAddonDisabledState(addon);
if (disabled !== undefined)

View File

@ -342,6 +342,8 @@ function DBAddonInternalPrototype()
{
this.applyCompatibilityUpdate =
function(aUpdate, aSyncCompatibility) {
let wasCompatible = this.isCompatible;
this.targetApplications.forEach(function(aTargetApp) {
aUpdate.targetApplications.forEach(function(aUpdateTarget) {
if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
@ -357,7 +359,9 @@ function DBAddonInternalPrototype()
this.multiprocessCompatible = aUpdate.multiprocessCompatible;
XPIDatabase.saveChanges();
}
XPIProvider.updateAddonDisabledState(this);
if (wasCompatible != this.isCompatible)
XPIProvider.updateAddonDisabledState(this);
};
this.toJSON =

View File

@ -0,0 +1,134 @@
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
const DATA = "data/signing_checks/";
const ID = "test@tests.mozilla.org";
Components.utils.import("resource://testing-common/httpd.js");
var gServer = new HttpServer();
gServer.start();
gServer.registerPathHandler("/update.rdf", function(request, response) {
let updateData = {};
updateData[ID] = [{
version: "2.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "4",
maxVersion: "6"
}]
}];
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(createUpdateRDF(updateData));
});
const SERVER = "127.0.0.1:" + gServer.identity.primaryPort;
Services.prefs.setCharPref("extensions.update.background.url", "http://" + SERVER + "/update.rdf");
function verifySignatures() {
return new Promise(resolve => {
let observer = (subject, topic, data) => {
Services.obs.removeObserver(observer, "xpi-signature-changed");
resolve(JSON.parse(data));
}
Services.obs.addObserver(observer, "xpi-signature-changed", false);
do_print("Verifying signatures");
let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
XPIscope.XPIProvider.verifySignatures();
});
}
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
// Start and stop the manager to initialise everything in the profile before
// actual testing
startupManager();
shutdownManager();
run_next_test();
}
// Updating the pref without changing the app version won't disable add-ons
// immediately but will after a signing check
add_task(function*() {
startupManager();
// Install the signed add-on
yield promiseInstallAllFiles([do_get_file(DATA + "unsigned_bootstrap_2.xpi")]);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
yield promiseShutdownManager();
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
startupManager();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
// Update checks shouldn't affect the add-on
yield AddonManagerInternal.backgroundUpdateCheck();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
let changes = yield verifySignatures();
do_check_eq(changes.disabled.length, 1);
do_check_eq(changes.disabled[0], ID);
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
addon.uninstall();
yield promiseShutdownManager();
});
// Updating the pref with changing the app version will disable add-ons
// immediately
add_task(function*() {
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
startupManager();
// Install the signed add-on
yield promiseInstallAllFiles([do_get_file(DATA + "unsigned_bootstrap_2.xpi")]);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
yield promiseShutdownManager();
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gAppInfo.version = 5.0
startupManager(true);
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
addon.uninstall();
yield promiseShutdownManager();
});

View File

@ -237,6 +237,7 @@ fail-if = buildapp == "mulet" || os == "android"
[test_pref_properties.js]
[test_registry.js]
[test_safemode.js]
[test_signed_updatepref.js]
[test_signed_verify.js]
[test_signed_inject.js]
[test_signed_install.js]

Some files were not shown because too many files have changed in this diff Show More