Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-05-01 10:27:52 -04:00
commit 9485e5d806
202 changed files with 4283 additions and 5393 deletions

View File

@ -1327,8 +1327,8 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
// Developer edition preferences
#ifdef MOZ_DEV_EDITION
pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
pref("browser.devedition.theme.enabled", true);
sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
sticky_pref("browser.devedition.theme.enabled", true);
#endif
// Developer edition promo preferences
@ -1443,6 +1443,15 @@ pref("devtools.performance.ui.show-idle-blocks", true);
pref("devtools.performance.ui.enable-memory", false);
pref("devtools.performance.ui.enable-framerate", true);
pref("devtools.performance.ui.show-jit-optimizations", false);
// If in aurora (40.0, will revert for 40.1), set default
// to retro mode.
// TODO bug 1160313
#if MOZ_UPDATE_CHANNEL == aurora
pref("devtools.performance.ui.retro-mode", true);
#else
pref("devtools.performance.ui.retro-mode", false);
#endif
// The default cache UI setting
pref("devtools.cache.disabled", false);
@ -1507,9 +1516,9 @@ pref("devtools.webaudioeditor.inspectorWidth", 300);
// Default theme ("dark" or "light")
#ifdef MOZ_DEV_EDITION
pref("devtools.theme", "dark");
sticky_pref("devtools.theme", "dark");
#else
pref("devtools.theme", "light");
sticky_pref("devtools.theme", "light");
#endif
// Display the introductory text
@ -1646,10 +1655,10 @@ pref("browser.newtabpage.rows", 3);
pref("browser.newtabpage.columns", 5);
// directory tiles download URL
pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v2/links/fetch/%LOCALE%");
pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
// endpoint to send newtab click and view pings
pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v2/links/");
pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);
@ -1863,7 +1872,7 @@ pref("browser.polaris.enabled", false);
pref("privacy.trackingprotection.ui.enabled", false);
#endif
#ifdef NIGHTLY_BUILD
#ifdef E10S_TESTING_ONLY
// At the moment, autostart.2 is used, while autostart.1 is unused.
// We leave it here set to false to reset users' defaults and allow
// us to change everybody to true in the future, when desired.
@ -1871,7 +1880,7 @@ pref("browser.tabs.remote.autostart.1", false);
pref("browser.tabs.remote.autostart.2", true);
#endif
#ifdef NIGHTLY_BUILD
#ifdef E10S_TESTING_ONLY
// Enable e10s add-on interposition by default.
pref("extensions.interposition.enabled", true);
pref("extensions.interposition.prefetching", true);
@ -1900,6 +1909,7 @@ pref("browser.readinglist.sidebarEverOpened", false);
pref("readinglist.scheduler.enabled", false);
pref("readinglist.server", "https://readinglist.services.mozilla.com/v1");
pref("browser.reader.detectedFirstArticle", false);
// Don't limit how many nodes we care about on desktop:
pref("reader.parse-node-limit", 0);

View File

@ -133,7 +133,7 @@ Site.prototype = {
if (this.link.targetedSite) {
this.node.setAttribute("suggested", true);
let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
let targetedSite = `<strong> ${this.link.targetedName} </strong>`;
this._querySelector(".newtab-suggested").innerHTML =
`<div class='newtab-suggested-bounds'> ${newTabString("suggested.button", [targetedSite])} </div>`;
}

View File

@ -555,7 +555,9 @@ addEventListener("unload", () => {
}, false);
addMessageListener("Browser:AppTab", function(message) {
docShell.isAppTab = message.data.isAppTab;
if (docShell) {
docShell.isAppTab = message.data.isAppTab;
}
});
let WebBrowserChrome = {

View File

@ -6,30 +6,6 @@ Components.utils.import("resource://gre/modules/Promise.jsm", this);
let chatbar = document.getElementById("pinnedchats");
function waitForCondition(condition, errorMsg) {
let deferred = Promise.defer();
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); deferred.resolve(); };
return deferred.promise;
}
add_chat_task(function* testOpenCloseChat() {
let chatbox = yield promiseOpenChat("http://example.com");
Assert.strictEqual(chatbox, chatbar.selectedChat);
@ -115,7 +91,7 @@ add_chat_task(function* testSecondTopLevelWindow() {
secondWindow.close();
});
// Test that chats are created in the correct window.
// Test that findChromeWindowForChats() returns the expected window.
add_chat_task(function* testChatWindowChooser() {
let chat = yield promiseOpenChat("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "first window has the chat");
@ -124,42 +100,34 @@ add_chat_task(function* testChatWindowChooser() {
let secondWindow = OpenBrowserWindow();
yield promiseOneEvent(secondWindow, "load");
Assert.equal(secondWindow, Chat.findChromeWindowForChats(null), "Second window is the preferred chat window");
Assert.equal(numChatsInWindow(secondWindow), 0, "second window starts with no chats");
yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(secondWindow), 1, "second window now has chats");
Assert.equal(numChatsInWindow(window), 1, "first window still has 1 chat");
chat.close();
// a bit heavy handed, but handy fixing bug 1090633
yield waitForCondition(function () !chat.parentNode, "chat has been detached");
Assert.equal(numChatsInWindow(window), 0, "first window now has no chats");
// now open another chat - it should still open in the second.
yield promiseOpenChat("http://example.com#3");
Assert.equal(numChatsInWindow(window), 0, "first window still has no chats");
Assert.equal(numChatsInWindow(secondWindow), 2, "second window has both chats");
// focus the first window, and check it will be selected for future chats.
// Bug 1090633 - there are lots of issues around focus, especially when the
// browser itself doesn't have focus. We can end up with
// Services.wm.getMostRecentWindow("navigator:browser") returning a different
// window than, say, Services.focus.activeWindow. But the focus manager isn't
// the point of this test.
// So we simply keep focusing the first window until it is reported as the
// most recent.
do {
dump("trying to force window to become the most recent.\n");
secondWindow.focus();
window.focus();
yield promiseWaitForFocus();
} while (Services.wm.getMostRecentWindow("navigator:browser") != window)
// focus the first window, and open yet another chat - it
// should open in the first window.
window.focus();
yield promiseWaitForFocus();
chat = yield promiseOpenChat("http://example.com#4");
Assert.equal(numChatsInWindow(window), 1, "first window got new chat");
chat.close();
Assert.equal(numChatsInWindow(window), 0, "first window has no chats");
Assert.equal(window, Chat.findChromeWindowForChats(null), "First window now the preferred chat window");
let privateWindow = OpenBrowserWindow({private: true});
yield promiseOneEvent(privateWindow, "load")
// open a last chat - the focused window can't accept
// chats (it's a private window), so the chat should open
// in the window that was selected before. This is known
// to be broken on Linux.
chat = yield promiseOpenChat("http://example.com#5");
let os = Services.appinfo.OS;
const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
let fn = BROKEN_WM_Z_ORDER ? todo : ok;
fn(numChatsInWindow(window) == 1, "first window got the chat");
chat.close();
// The focused window can't accept chats (it's a private window), so the
// chat should open in the window that was selected before. This will be
// either window or secondWindow (linux may choose a different one) but the
// point is that the window is *not* the private one.
Assert.ok(Chat.findChromeWindowForChats(null) == window ||
Chat.findChromeWindowForChats(null) == secondWindow,
"Private window isn't selected for new chats.");
privateWindow.close();
secondWindow.close();
});

View File

@ -10,12 +10,16 @@ const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/cha
// Is the currently opened tab focused?
function isTabFocused() {
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
return Services.focus.focusedWindow == tabb.contentWindow;
// focus sucks in tests - our window may have lost focus.
let elt = Services.focus.getFocusedElementForWindow(window, false, {});
return elt == tabb;
}
// Is the specified chat focused?
function isChatFocused(chat) {
return chat.chatbar._isChatFocused(chat);
// focus sucks in tests - our window may have lost focus.
let elt = Services.focus.getFocusedElementForWindow(window, false, {});
return elt == chat.content;
}
let chatbar = document.getElementById("pinnedchats");

View File

@ -32,19 +32,29 @@ add_task(function* test_reader_button() {
TEST_PREFS.forEach(([name, value]) => {
Services.prefs.setBoolPref(name, value);
});
Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", false);
let tab = gBrowser.selectedTab = gBrowser.addTab();
is_element_hidden(readerButton, "Reader mode button is not present on a new tab");
ok(!UITour.isInfoOnTarget(window, "readerMode-urlBar"),
"Info panel shouldn't appear without the reader mode button");
ok(!Services.prefs.getBoolPref("browser.reader.detectedFirstArticle"),
"Shouldn't have detected the first article");
// Point tab to a test page that is reader-able.
let url = TEST_PATH + "readerModeArticle.html";
yield promiseTabLoadEvent(tab, url);
yield promiseWaitForCondition(() => !readerButton.hidden);
is_element_visible(readerButton, "Reader mode button is present on a reader-able page");
ok(UITour.isInfoOnTarget(window, "readerMode-urlBar"),
"Info panel should be anchored at the reader mode button");
ok(Services.prefs.getBoolPref("browser.reader.detectedFirstArticle"),
"Should have detected the first article");
// Switch page into reader mode.
readerButton.click();
yield promiseTabLoadEvent(tab);
ok(!UITour.isInfoOnTarget(window, "readerMode-urlBar"), "Info panel should have closed");
let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
ok(readerUrl.startsWith("about:reader"), "about:reader loaded after clicking reader mode button");

View File

@ -18,6 +18,8 @@ gDirectorySource = "data:application/json," + JSON.stringify({
});
function runTests() {
let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
DirectoryLinksProvider.getFrecentSitesName = () => "";
let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
NewTabUtils.isTopPlacesSite = (site) => false;
@ -84,4 +86,5 @@ function runTests() {
yield blockCell(1);
yield addNewTabPageTab();
checkGrid("1,2,3,4,5,6,7,8,9");
DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
}

View File

@ -26,8 +26,11 @@ gDirectorySource = "data:application/json," + JSON.stringify({
});
function runTests() {
let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
DirectoryLinksProvider.getFrecentSitesName = () => "";
let origEnhanced = NewTabUtils.allPages.enhanced;
registerCleanupFunction(() => {
DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
Services.prefs.clearUserPref(PRELOAD_PREF);
NewTabUtils.allPages.enhanced = origEnhanced;
});

View File

@ -1,15 +1,14 @@
# We still need to fix warnings in this file.
MozLoopWorker.js
# This file currently uses es7 features
MozLoopAPI.jsm
# This file currently uses a non-standard (and not on a standards track)
# if statement within catch.
modules/MozLoopWorker.js
# This file currently uses es7 features eslint issue:
# https://github.com/eslint/espree/issues/125
modules/MozLoopAPI.jsm
# Libs we don't need to check
content/libs
content/shared/libs
standalone/content/libs
standalone/node_modules
# We should look at turning these on when we fix the warnings
test/xpcshell
test/mochitest
# Libs we don't need to check
test/shared/vendor

View File

@ -1,3 +1,5 @@
// Note: there are extra allowances for files used solely in Firefox desktop,
// see content/js/.eslintrc and modules/.eslintrc
{
"plugins": [
"react"
@ -5,18 +7,6 @@
"ecmaFeatures": {
"forOf": true,
"jsx": true,
// These are on for this directory for .jsm and content/js files.
// If adding more items here, consider turning them off for the following
// files if they aren't supported by all browsers.
// content/shared/.eslintrc
// content/standalone/.eslintrc
"blockBindings": true,
"arrowFunctions": true,
"destructuring": true,
"generators": true,
"spread": true,
"restParams": true,
"objectLiteralShorthandMethods": true
},
"env": {
"browser": true,
@ -43,49 +33,50 @@
// problems they find, one at a time.
// Eslint built-in rules are documented at <http://eslint.org/docs/rules/>
"camelcase": 0,
"comma-dangle": 0,
"comma-spacing": 0,
"consistent-return": 0,
"curly": 0,
"dot-notation": 0,
"eol-last": 0,
"eqeqeq": 0,
"global-strict": 0,
"key-spacing": 0,
"new-cap": 0,
"no-catch-shadow": 0,
"no-console": 0,
"no-empty": 0,
"no-extra-bind": 0,
"no-extra-boolean-cast": 0,
"no-extra-semi": 0,
"no-multi-spaces": 0,
"no-new": 0,
"no-redeclare": 0,
"no-return-assign": 0,
"no-shadow": 0,
"no-spaced-func": 0,
"no-trailing-spaces": 0,
"no-undef": 0,
"no-underscore-dangle": 0,
"no-unused-expressions": 0,
"no-unused-vars": 0,
"no-use-before-define": 0,
"no-wrap-func": 0,
"quotes": 0,
"semi": 0,
"semi-spacing": 0,
"space-infix-ops": 0,
"space-return-throw-case": 0,
"strict": 0,
"yoda": 0,
"camelcase": 0, // TODO: Remove (use default)
"comma-dangle": 0, // TODO: Remove (use default)
"comma-spacing": 0, // TODO: Remove (use default)
"consistent-return": 0, // TODO: Remove (use default)
"curly": 0, // TODO: Remove (use default)
"dot-notation": 0, // TODO: Remove (use default)
"eol-last": 0, // TODO: Remove (use default)
"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": 0, // TODO: Remove (use default)
"new-cap": 0, // TODO: Remove (use default)
"no-catch-shadow": 0, // TODO: Remove (use default)
"no-console": 0, // Leave as 0. We use console logging in content code.
"no-empty": 0, // TODO: Remove (use default)
"no-extra-bind": 0, // Leave as 0
"no-extra-boolean-cast": 0, // TODO: Remove (use default)
"no-extra-semi": 0, // TODO: Remove (use default)
"no-multi-spaces": 0, // TBD.
"no-new": 0, // TODO: Remove (use default)
"no-redeclare": 0, // TODO: Remove (use default)
"no-return-assign": 0, // TODO: Remove (use default)
"no-shadow": 0, // TODO: Remove (use default)
"no-spaced-func": 0, // TODO: Remove (use default)
"no-trailing-spaces": 0, // TODO: Remove (use default)
"no-undef": 0, // TODO: Remove (use default)
"no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables.
"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-wrap-func": 0, // TODO: Remove (use default)
"quotes": 0, // [2, "double", "avoid-escape"],
"semi": 0, // TODO: Remove (use default)
"semi-spacing": 0, // TODO: Remove (use default)
"space-infix-ops": 0, // TODO: Remove (use default)
"space-return-throw-case": 0, // TODO: Remove (use default)
"strict": 0, // [2, "function"],
"yoda": 0, // [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"],
"react/jsx-no-undef": 2,
// Need to fix instances where this is failing.
"react/jsx-sort-props": 0,
"react/jsx-sort-prop-types": 0,
"react/jsx-uses-vars": 2,
// Need to fix the couple of instances which don't
// currently pass this rule.
@ -101,6 +92,7 @@
"react/react-in-jsx-scope": 0,
// These ones we don't want to ever enable
"react/display-name": 0,
"react/jsx-boolean-value": 0,
"react/no-multi-comp": 0
}
}

View File

@ -54,7 +54,7 @@ If you install eslint and the react plugin globally:
You can also run it by hand in the browser/components/loop directory:
eslint -ext .js -ext .jsx --ext .jsm .
eslint --ext .js --ext .jsx --ext .jsm .
Front-End Unit Tests
====================

View File

@ -0,0 +1,15 @@
{
"ecmaFeatures": {
// These are on for this directory for .jsm and content/js files.
"blockBindings": true,
"arrowFunctions": true,
"destructuring": true,
"generators": true,
"spread": true,
"restParams": true,
"objectLiteralShorthandMethods": true
},
"rules": {
"generator-star-spacing": [2, "after"]
}
}

View File

@ -222,7 +222,7 @@ loop.contacts = (function(_, mozL10n) {
"disabled": !this.props.canEdit }),
onClick: this.onItemClick, "data-action": "remove"},
React.createElement("i", {className: "icon icon-remove"}),
mozL10n.get("remove_contact_menu_button")
mozL10n.get("remove_contact_menu_button2")
)
)
);
@ -587,7 +587,7 @@ loop.contacts = (function(_, mozL10n) {
React.createElement(ButtonGroup, null,
React.createElement(Button, {caption: this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button"),
: mozL10n.get("import_contacts_button2"),
disabled: this.state.importBusy,
onClick: this.handleImportButtonClick},
React.createElement("div", {className: cx({"contact-import-spinner": true,

View File

@ -222,7 +222,7 @@ loop.contacts = (function(_, mozL10n) {
"disabled": !this.props.canEdit })}
onClick={this.onItemClick} data-action="remove">
<i className="icon icon-remove" />
{mozL10n.get("remove_contact_menu_button")}
{mozL10n.get("remove_contact_menu_button2")}
</li>
</ul>
);
@ -587,7 +587,7 @@ loop.contacts = (function(_, mozL10n) {
<ButtonGroup>
<Button caption={this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button")}
: mozL10n.get("import_contacts_button2")}
disabled={this.state.importBusy}
onClick={this.handleImportButtonClick}>
<div className={cx({"contact-import-spinner": true,

View File

@ -11,10 +11,11 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var ROOM_STATES = loop.store.ROOM_STATES;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
/**
@ -312,13 +313,25 @@ loop.roomViews = (function(mozL10n) {
var thumbnail = URL && URL.thumbnail || "";
var URLDescription = URL && URL.description || "";
var location = URL && URL.location || "";
var locationData = null;
if (location) {
locationData = sharedUtils.formatURL(location);
}
if (!locationData) {
return null;
}
return (
React.createElement("div", {className: "room-context"},
React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}),
React.createElement("div", {className: "room-context-content"},
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
React.createElement("div", {className: "room-context-description"}, URLDescription),
React.createElement("a", {className: "room-context-url", href: location, target: "_blank"}, location),
React.createElement("a", {className: "room-context-url",
href: location,
target: "_blank",
title: locationData.location}, locationData.hostname),
this.props.roomData.roomDescription ?
React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
null,

View File

@ -11,10 +11,11 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var ROOM_STATES = loop.store.ROOM_STATES;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
/**
@ -312,13 +313,25 @@ loop.roomViews = (function(mozL10n) {
var thumbnail = URL && URL.thumbnail || "";
var URLDescription = URL && URL.description || "";
var location = URL && URL.location || "";
var locationData = null;
if (location) {
locationData = sharedUtils.formatURL(location);
}
if (!locationData) {
return null;
}
return (
<div className="room-context">
<img className="room-context-thumbnail" src={thumbnail}/>
<div className="room-context-content">
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
<div className="room-context-description">{URLDescription}</div>
<a className="room-context-url" href={location} target="_blank">{location}</a>
<a className="room-context-url"
href={location}
target="_blank"
title={locationData.location}>{locationData.hostname}</a>
{this.props.roomData.roomDescription ?
<div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
null}

View File

@ -1,14 +0,0 @@
{
"ecmaFeatures": {
// Turn off top-level items needed for the jsm files, but not wanted
// for shared code as we can't support them.
"blockBindings": false,
"arrowFunctions": false,
"destructuring": false,
"forOf": true,
"generators": false,
"spread": false,
"restParams": false,
"objectLiteralShorthandMethods": false
}
}

View File

@ -789,7 +789,7 @@ loop.OTSdkDriver = (function() {
bucket = buckets.MORE_THAN_5M;
}
this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH", bucket);
this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", bucket);
this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED);
this._connectionLengthNotedCalls++;
@ -857,7 +857,7 @@ loop.OTSdkDriver = (function() {
return;
}
this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE", bucket);
this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE_1", bucket);
}
};

View File

@ -271,6 +271,33 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
};
}
/**
* Formats a url for display purposes. This includes converting the
* domain to punycode, and then decoding the url.
*
* @param {String} url The url to format.
* @return {Object} An object containing the hostname and full location.
*/
function formatURL(url) {
// We're using new URL to pass this through the browser's ACE/punycode
// processing system. If the browser considers a url to need to be
// punycode encoded for it to be displayed, then new URL will do that for
// us. This saves us needing our own punycode library.
var urlObject;
try {
urlObject = new URL(url);
} catch (ex) {
console.error("Error occurred whilst parsing URL:", ex);
return null;
}
// Finally, ensure we look good.
return {
hostname: urlObject.hostname,
location: decodeURI(urlObject.href)
};
}
/**
* Generates and opens a mailto: url with call URL information prefilled.
* Note: This only works for Desktop.
@ -536,6 +563,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
composeCallUrlEmail: composeCallUrlEmail,
formatDate: formatDate,
formatURL: formatURL,
getBoolPreference: getBoolPreference,
getOS: getOS,
getOSVersion: getOSVersion,

View File

@ -0,0 +1,17 @@
{
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"destructuring": true,
"generators": true,
"restParams": true,
"spread": true,
"objectLiteralShorthandMethods": true,
},
"rules": {
"generator-star-spacing": [2, "after"],
// We should fix the errors and enable this (set to 2)
"no-var": 0,
"strict": [2, "global"]
}
}

View File

@ -13,18 +13,18 @@ BROWSER_CHROME_MANIFESTS += [
]
EXTRA_JS_MODULES.loop += [
'CardDavImporter.jsm',
'content/shared/js/crypto.js',
'content/shared/js/utils.js',
'GoogleImporter.jsm',
'LoopCalls.jsm',
'LoopContacts.jsm',
'LoopRooms.jsm',
'LoopStorage.jsm',
'MozLoopAPI.jsm',
'MozLoopPushHandler.jsm',
'MozLoopService.jsm',
'MozLoopWorker.js',
'modules/CardDavImporter.jsm',
'modules/GoogleImporter.jsm',
'modules/LoopCalls.jsm',
'modules/LoopContacts.jsm',
'modules/LoopRooms.jsm',
'modules/LoopStorage.jsm',
'modules/MozLoopAPI.jsm',
'modules/MozLoopPushHandler.jsm',
'modules/MozLoopService.jsm',
'modules/MozLoopWorker.js',
]
with Files('**'):

View File

@ -1,14 +0,0 @@
{
"ecmaFeatures": {
// Turn off top-level items needed for the jsm files, but not wanted
// for shared code as we can't support them.
"blockBindings": false,
"arrowFunctions": false,
"destructuring": false,
"forOf": true,
"generators": false,
"spread": false,
"restParams": false,
"objectLiteralShorthandMethods": false
}
}

View File

@ -16,6 +16,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
var ROOM_STATES = loop.store.ROOM_STATES;
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var StandaloneRoomInfoArea = React.createClass({displayName: "StandaloneRoomInfoArea",
@ -248,7 +249,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
return null;
}
var location = this.props.roomContextUrl.location;
var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
if (!locationInfo) {
return null;
}
var cx = React.addons.classSet;
@ -262,9 +266,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("img", {src: this.props.roomContextUrl.thumbnail}),
React.createElement("div", {className: "standalone-context-url-description-wrapper"},
this.props.roomContextUrl.description,
React.createElement("br", null), React.createElement("a", {href: location,
React.createElement("br", null), React.createElement("a", {href: locationInfo.location,
onClick: this.recordClick,
target: "_blank"}, location)
target: "_blank",
title: locationInfo.location}, locationInfo.hostname)
)
)
);

View File

@ -16,6 +16,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
var ROOM_STATES = loop.store.ROOM_STATES;
var sharedActions = loop.shared.actions;
var sharedMixins = loop.shared.mixins;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var StandaloneRoomInfoArea = React.createClass({
@ -248,7 +249,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
return null;
}
var location = this.props.roomContextUrl.location;
var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
if (!locationInfo) {
return null;
}
var cx = React.addons.classSet;
@ -262,9 +266,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
<img src={this.props.roomContextUrl.thumbnail} />
<div className="standalone-context-url-description-wrapper">
{this.props.roomContextUrl.description}
<br /><a href={location}
<br /><a href={locationInfo.location}
onClick={this.recordClick}
target="_blank">{location}</a>
target="_blank"
title={locationInfo.location}>{locationInfo.hostname}</a>
</div>
</div>
);

View File

@ -12,8 +12,8 @@
},
"dependencies": {},
"devDependencies": {
"eslint": "0.18.x",
"eslint-plugin-react": "2.0.x",
"eslint": "0.20.x",
"eslint-plugin-react": "2.2.x",
"express": "3.x"
},
"scripts": {

View File

@ -0,0 +1,7 @@
{
"rules": {
// This is useful for some of the tests, e.g.
// expect(new Foo()).to.Throw(/error/)
"no-new": 0
}
}

View File

@ -242,6 +242,23 @@ describe("loop.roomViews", function () {
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
});
it("should format the context url for display", function() {
sandbox.stub(sharedUtils, "formatURL").returns({
location: "location",
hostname: "hostname"
});
view = mountTestComponent({
showContext: true,
roomData: {
roomContextUrls: [fakeContextURL]
}
});
expect(view.getDOMNode().querySelector(".room-context-url").textContent)
.eql("hostname");
});
});
});

View File

@ -0,0 +1,17 @@
{
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"destructuring": true,
"generators": true,
"restParams": true,
"spread": true,
"objectLiteralShorthandMethods": true,
},
"rules": {
"generator-star-spacing": [2, "after"],
// We should fix the errors and enable this (set to 2)
"no-var": 0,
"strict": [2, "global"]
}
}

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
const kAuth = {

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {GoogleImporter} = Cu.import("resource:///modules/loop/GoogleImporter.jsm", {});
let importer = new GoogleImporter();

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {LoopContacts} = Cu.import("resource:///modules/loop/LoopContacts.jsm", {});
const {LoopStorage} = Cu.import("resource:///modules/loop/LoopStorage.jsm", {});
@ -221,8 +223,13 @@ add_task(function* () {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result.length, toRetrieve.length, "Result list should be the same " +
"size as the list of items to retrieve");
function resultFilter(c) {
return c._guid == this._guid;
}
for (let contact of toRetrieve) {
let found = result.filter(c => c._guid == contact._guid);
let found = result.filter(resultFilter.bind(contact));
Assert.ok(found.length, "Contact " + contact._guid + " should be in the list");
compareContacts(found[0], contact);
}

View File

@ -6,6 +6,8 @@
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);

View File

@ -6,6 +6,8 @@
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);

View File

@ -1,21 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This is an integration test from navigator.mozLoop through to the end
* effects - rather than just testing MozLoopAPI alone.
*/
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);
add_task(function* test_mozLoop_pluralStrings() {
Assert.ok(gMozLoopAPI, "mozLoop should exist");
var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
"This window will close in {{countdown}} seconds");
Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
"This window will close in {{countdown}} second");
});
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This is an integration test from navigator.mozLoop through to the end
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);
add_task(function* test_mozLoop_pluralStrings() {
Assert.ok(gMozLoopAPI, "mozLoop should exist");
var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
"This window will close in {{countdown}} seconds");
Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
"This window will close in {{countdown}} second");
});

View File

@ -6,6 +6,8 @@
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);

View File

@ -6,6 +6,8 @@
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
const {SocialService} = Cu.import("resource://gre/modules/SocialService.jsm", {});

View File

@ -4,6 +4,9 @@
/*
* This file contains tests for the mozLoop telemetry API.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);
@ -23,7 +26,7 @@ add_task(function* test_initialize() {
* Tests that enumerated bucket histograms exist and can be updated.
*/
add_task(function* test_mozLoop_telemetryAdd_buckets() {
let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH";
let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1";
let histogram = Services.telemetry.getHistogramById(histogramId);
let CONN_LENGTH = gMozLoopAPI.TWO_WAY_MEDIA_CONN_LENGTH;
@ -49,7 +52,7 @@ add_task(function* test_mozLoop_telemetryAdd_buckets() {
});
add_task(function* test_mozLoop_telemetryAdd_sharing_buckets() {
let histogramId = "LOOP_SHARING_STATE_CHANGE";
let histogramId = "LOOP_SHARING_STATE_CHANGE_1";
let histogram = Services.telemetry.getHistogramById(histogramId);
const SHARING_STATES = gMozLoopAPI.SHARING_STATE_CHANGE;

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const HAWK_TOKEN_LENGTH = 64;
const {
LOOP_SESSION_TYPE,
@ -123,8 +125,8 @@ function loadLoopPanel(aOverrideOptions = {}) {
let loopPanel = document.getElementById("loop-notification-panel");
loopPanel.setAttribute("animate", "false");
// Now get the actual API.
yield promiseGetMozLoopAPI();
// Now get the actual API loaded into gMozLoopAPI.
return promiseGetMozLoopAPI();
}
function promiseOAuthParamsSetup(baseURL, params) {
@ -319,7 +321,7 @@ const mockDb = {
callback(null, details);
},
remove: function(guid, callback) {
if (!guid in this._store) {
if (!(guid in this._store)) {
callback(new Error("Could not find _guid '" + guid + "' in database"));
return;
}

View File

@ -473,7 +473,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWith(mozLoop.telemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S);
});
@ -485,7 +485,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWith(mozLoop.telemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S);
});
@ -497,7 +497,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWith(mozLoop.telemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M);
});
@ -508,7 +508,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWith(mozLoop.telemetryAddValue,
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
"LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M);
});
@ -530,7 +530,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
"LOOP_SHARING_STATE_CHANGE",
"LOOP_SHARING_STATE_CHANGE_1",
mozLoop.SHARING_STATE_CHANGE.WINDOW_ENABLED);
});
@ -539,7 +539,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
"LOOP_SHARING_STATE_CHANGE",
"LOOP_SHARING_STATE_CHANGE_1",
mozLoop.SHARING_STATE_CHANGE.BROWSER_ENABLED);
});
@ -548,7 +548,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
"LOOP_SHARING_STATE_CHANGE",
"LOOP_SHARING_STATE_CHANGE_1",
mozLoop.SHARING_STATE_CHANGE.WINDOW_DISABLED);
});
@ -557,7 +557,7 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledOnce(mozLoop.telemetryAddValue);
sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
"LOOP_SHARING_STATE_CHANGE",
"LOOP_SHARING_STATE_CHANGE_1",
mozLoop.SHARING_STATE_CHANGE.BROWSER_DISABLED);
});
});

View File

@ -145,6 +145,31 @@ describe("loop.shared.utils", function() {
});
});
describe("#formatURL", function() {
it("should decode encoded URIs", function() {
expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar"))
.eql({
location: "http://invalid.com/?a=Foo Bar",
hostname: "invalid.com"
});
});
it("should change some idn urls to ascii encoded", function() {
// Note, this is based on the browser's list of what does/doesn't get
// altered for punycode, so if the list changes this could change in the
// future.
expect(sharedUtils.formatURL("http://\u0261oogle.com/"))
.eql({
location: "http://xn--oogle-qmc.com/",
hostname: "xn--oogle-qmc.com"
});
});
it("should return null if it the url is not valid", function() {
expect(sharedUtils.formatURL("hinvalid//url")).eql(null);
});
});
describe("#composeCallUrlEmail", function() {
var composeEmail;

View File

@ -13,6 +13,7 @@ describe("loop.standaloneRoomViews", function() {
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sandbox, dispatcher, activeRoomStore, feedbackStore, dispatch;
@ -96,6 +97,26 @@ describe("loop.standaloneRoomViews", function() {
expect(view.getDOMNode().querySelector(".standalone-context-url")).not.eql(null);
});
it("should format the url for display", function() {
sandbox.stub(sharedUtils, "formatURL").returns({
location: "location",
hostname: "hostname"
});
var view = mountTestComponent({
roomName: "Mike's room",
roomContextUrls: [{
description: "Mark's super page",
location: "http://invalid.com",
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
}]
});
expect(view.getDOMNode()
.querySelector(".standalone-context-url-description-wrapper > a").textContent)
.eql("hostname");
});
it("should not display context information if no urls are supplied", function() {
var view = mountTestComponent({
roomName: "Mike's room"

View File

@ -0,0 +1,17 @@
{
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"destructuring": true,
"generators": true,
"restParams": true,
"spread": true,
"objectLiteralShorthandMethods": true,
},
"rules": {
"generator-star-spacing": [2, "after"],
// We should fix the errors and enable this (set to 2)
"no-var": 0,
"strict": [2, "global"]
}
}

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

View File

@ -1,222 +1,223 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
{
let dummyCallback = () => {};
let mockWebSocket = new MockWebSocketChannel();
let pushServerRequestCount = 0;
add_test(function test_initalize_offline() {
Services.io.offline = true;
do_check_false(MozLoopPushHandler.initialize());
Services.io.offline = false;
run_next_test();
});
"use strict";
add_test(function test_initalize_missing_chanid() {
Assert.throws(() => MozLoopPushHandler.register(null, dummyCallback, dummyCallback));
run_next_test();
});
let dummyCallback = () => {};
let mockWebSocket = new MockWebSocketChannel();
let pushServerRequestCount = 0;
add_test(function test_initalize_missing_regcallback() {
Assert.throws(() => MozLoopPushHandler.register("chan-1", null, dummyCallback));
run_next_test();
});
add_test(function test_initalize_offline() {
Services.io.offline = true;
do_check_false(MozLoopPushHandler.initialize());
Services.io.offline = false;
run_next_test();
});
add_test(function test_initalize_missing_notifycallback() {
Assert.throws(() => MozLoopPushHandler.register("chan-1", dummyCallback, null));
run_next_test();
});
add_test(function test_initalize_missing_chanid() {
Assert.throws(() => MozLoopPushHandler.register(null, dummyCallback, dummyCallback));
run_next_test();
});
add_test(function test_initalize_websocket() {
do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
MozLoopPushHandler.register(
"chan-1",
function(err, url, id) {
Assert.equal(err, null, "err should be null to indicate success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
"Should have the url from preferences");
Assert.equal(mockWebSocket.origin, kServerPushUrl,
"Should have the origin url from preferences");
Assert.equal(mockWebSocket.protocol, "push-notification",
"Should have the protocol set to push-notifications");
mockWebSocket.notify(15);
},
function(version, id) {
Assert.equal(version, 15, "Should have version number 15");
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
run_next_test();
});
});
add_test(function test_initalize_missing_regcallback() {
Assert.throws(() => MozLoopPushHandler.register("chan-1", null, dummyCallback));
run_next_test();
});
add_test(function test_register_twice_same_channel() {
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.equal(err, null, "Should return null for success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
"Should have the url from preferences");
Assert.equal(mockWebSocket.origin, kServerPushUrl,
"Should have the origin url from preferences");
Assert.equal(mockWebSocket.protocol, "push-notification",
"Should have the protocol set to push-notifications");
add_test(function test_initalize_missing_notifycallback() {
Assert.throws(() => MozLoopPushHandler.register("chan-1", dummyCallback, null));
run_next_test();
});
// Register again for the same channel
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.equal(err, null, "Should return null for success");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
run_next_test();
},
dummyCallback
);
},
dummyCallback
);
});
// Test that the PushHander will re-connect after the near-end disconnect.
// The uaID is cleared to force re-registration of all notification channels.
add_test(function test_reconnect_websocket() {
MozLoopPushHandler.uaID = undefined;
mockWebSocket.stop();
// Previously registered onRegistration callbacks will fire and be checked (see above).
});
// Test that the PushHander will re-connect after the far-end disconnect.
// The uaID is cleared to force re-regsitration of all notification channels.
add_test(function test_reopen_websocket() {
MozLoopPushHandler.uaID = undefined;
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
mockWebSocket.serverClose();
// Previously registered onRegistration callbacks will fire and be checked (see above).
});
// Force a re-registration cycle and have the PushServer return a 500.
// A retry should occur and the registration then complete.
add_test(function test_retry_registration() {
MozLoopPushHandler.uaID = undefined;
mockWebSocket.initRegStatus = 500;
mockWebSocket.stop();
});
add_test(function test_reconnect_no_registration() {
let regCnt = 0;
MozLoopPushHandler.shutdown();
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
MozLoopPushHandler.register(
"test-chan",
function(err, url, id) {
Assert.equal(++regCnt, 1, "onRegistered should only be called once");
Assert.equal(err, null, "err should be null to indicate success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "test-chan", "Should have channel id = test-chan");
mockWebSocket.stop();
setTimeout(run_next_test(), 0);
},
dummyCallback
);
});
add_test(function test_ping_websocket() {
let pingReceived = false,
socketClosed = false;
mockWebSocket.defaultMsgHandler = (msg) => {
pingReceived = true;
// Do not send a ping response.
}
mockWebSocket.close = () => {
socketClosed = true;
}
MozLoopPushHandler.shutdown();
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
MozLoopPushHandler.register(
"test-chan",
function(err, url) {
Assert.equal(err, null, "err should be null to indicate success");
waitForCondition(() => pingReceived).then(() => {
waitForCondition(() => socketClosed).then(() => {
run_next_test();
}, () => {
do_throw("should have closed the websocket");
});
}, () => {
do_throw("should have sent ping");
});
},
dummyCallback
);
});
add_test(function test_retry_pushurl() {
MozLoopPushHandler.shutdown();
loopServer.registerPathHandler("/push-server-config", (request, response) => {
// The PushHandler should retry the request for the push-server-config for
// each of these cases without throwing an error.
let n = 0;
switch (++pushServerRequestCount) {
case ++n:
// Non-200 response
response.setStatusLine(null, 500, "Retry");
response.processAsync();
response.finish();
break;
case ++n:
// missing parameter
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: null}));
response.processAsync();
response.finish();
break;
case ++n:
// json parse error
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
break;
case ++n:
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
response.processAsync();
response.finish();
run_next_test();
break;
}
add_test(function test_initalize_websocket() {
do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
MozLoopPushHandler.register(
"chan-1",
function(err, url, id) {
Assert.equal(err, null, "err should be null to indicate success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
"Should have the url from preferences");
Assert.equal(mockWebSocket.origin, kServerPushUrl,
"Should have the origin url from preferences");
Assert.equal(mockWebSocket.protocol, "push-notification",
"Should have the protocol set to push-notifications");
mockWebSocket.notify(15);
},
function(version, id) {
Assert.equal(version, 15, "Should have version number 15");
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
run_next_test();
});
});
do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
});
add_test(function test_register_twice_same_channel() {
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.equal(err, null, "Should return null for success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
"Should have the url from preferences");
Assert.equal(mockWebSocket.origin, kServerPushUrl,
"Should have the origin url from preferences");
Assert.equal(mockWebSocket.protocol, "push-notification",
"Should have the protocol set to push-notifications");
function run_test() {
setupFakeLoopServer();
// Register again for the same channel
MozLoopPushHandler.register(
"chan-2",
function(err, url, id) {
Assert.equal(err, null, "Should return null for success");
Assert.equal(id, "chan-2", "Should have channel id = chan-2");
run_next_test();
},
dummyCallback
);
},
dummyCallback
);
});
loopServer.registerPathHandler("/push-server-config", (request, response) => {
// Test that the PushHander will re-connect after the near-end disconnect.
// The uaID is cleared to force re-registration of all notification channels.
add_test(function test_reconnect_websocket() {
MozLoopPushHandler.uaID = undefined;
mockWebSocket.stop();
// Previously registered onRegistration callbacks will fire and be checked (see above).
});
// Test that the PushHander will re-connect after the far-end disconnect.
// The uaID is cleared to force re-regsitration of all notification channels.
add_test(function test_reopen_websocket() {
MozLoopPushHandler.uaID = undefined;
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
mockWebSocket.serverClose();
// Previously registered onRegistration callbacks will fire and be checked (see above).
});
// Force a re-registration cycle and have the PushServer return a 500.
// A retry should occur and the registration then complete.
add_test(function test_retry_registration() {
MozLoopPushHandler.uaID = undefined;
mockWebSocket.initRegStatus = 500;
mockWebSocket.stop();
});
add_test(function test_reconnect_no_registration() {
let regCnt = 0;
MozLoopPushHandler.shutdown();
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
MozLoopPushHandler.register(
"test-chan",
function(err, url, id) {
Assert.equal(++regCnt, 1, "onRegistered should only be called once");
Assert.equal(err, null, "err should be null to indicate success");
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
Assert.equal(id, "test-chan", "Should have channel id = test-chan");
mockWebSocket.stop();
setTimeout(run_next_test(), 0);
},
dummyCallback
);
});
add_test(function test_ping_websocket() {
let pingReceived = false,
socketClosed = false;
mockWebSocket.defaultMsgHandler = (msg) => {
pingReceived = true;
// Do not send a ping response.
}
mockWebSocket.close = () => {
socketClosed = true;
}
MozLoopPushHandler.shutdown();
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
MozLoopPushHandler.register(
"test-chan",
function(err, url) {
Assert.equal(err, null, "err should be null to indicate success");
waitForCondition(() => pingReceived).then(() => {
waitForCondition(() => socketClosed).then(() => {
run_next_test();
}, () => {
do_throw("should have closed the websocket");
});
}, () => {
do_throw("should have sent ping");
});
},
dummyCallback
);
});
add_test(function test_retry_pushurl() {
MozLoopPushHandler.shutdown();
loopServer.registerPathHandler("/push-server-config", (request, response) => {
// The PushHandler should retry the request for the push-server-config for
// each of these cases without throwing an error.
let n = 0;
switch (++pushServerRequestCount) {
case ++n:
// Non-200 response
response.setStatusLine(null, 500, "Retry");
response.processAsync();
response.finish();
break;
case ++n:
// missing parameter
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: null}));
response.processAsync();
response.finish();
break;
case ++n:
// json parse error
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
break;
case ++n:
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
response.processAsync();
response.finish();
});
Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
Services.prefs.setIntPref("loop.ping.interval", 50); // 50 ms
Services.prefs.setIntPref("loop.ping.timeout", 20); // 20 ms
run_next_test();
break;
}
});
do_register_cleanup(function() {
Services.prefs.clearUserPref("services.push.serverULR");
Services.prefs.clearUserPref("loop.retry_delay.start");
Services.prefs.clearUserPref("loop.retry_delay.limit");
Services.prefs.clearUserPref("loop.ping.interval");
Services.prefs.clearUserPref("loop.ping.timeout");
});
do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
});
run_next_test();
};
}
function run_test() {
setupFakeLoopServer();
loopServer.registerPathHandler("/push-server-config", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
response.processAsync();
response.finish();
});
Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
Services.prefs.setIntPref("loop.ping.interval", 50); // 50 ms
Services.prefs.setIntPref("loop.ping.timeout", 20); // 20 ms
do_register_cleanup(function() {
Services.prefs.clearUserPref("services.push.serverULR");
Services.prefs.clearUserPref("loop.retry_delay.start");
Services.prefs.clearUserPref("loop.retry_delay.limit");
Services.prefs.clearUserPref("loop.ping.interval");
Services.prefs.clearUserPref("loop.ping.timeout");
});
run_next_test();
};

View File

@ -2,6 +2,8 @@
* 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/. */
"use strict";
Cu.import("resource://services-common/utils.js");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/Chat.jsm");

View File

@ -2,6 +2,8 @@
* 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/. */
"use strict";
const { LoopCallsInternal } = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "Chat",

View File

@ -2,6 +2,8 @@
* 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/. */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let openChatOrig = Chat.open;

View File

@ -2,6 +2,8 @@
* 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/. */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let openChatOrig = Chat.open;
@ -41,7 +43,7 @@ add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
waitForCondition(function() opened).then(() => {
waitForCondition(() => opened).then(() => {
run_next_test();
}, () => {
do_throw("should have opened a chat window");

View File

@ -2,6 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/* global Services, Assert */
"use strict";
const kGuestKeyPref = "loop.key";
const kFxAKeyPref = "loop.key.fxa";

View File

@ -1,13 +1,15 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let startTimerCalled = false;
/**
* Tests that registration doesn't happen when the expiry time is
* not set.
*/
add_task(function test_initialize_no_expiry() {
add_task(function* test_initialize_no_expiry() {
startTimerCalled = false;
let initializedPromise = yield MozLoopService.initialize();

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test_locale() {
// Set the pref to something controlled.
Services.prefs.setCharPref("general.useragent.locale", "ab-CD");

View File

@ -2,6 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/*global XPCOMUtils, Services, Assert */
"use strict";
var fakeCharPrefName = "color";
var fakeBoolPrefName = "boolean";
var fakePrefValue = "green";

View File

@ -2,6 +2,8 @@
* 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/. */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
@ -20,7 +22,7 @@ add_test(function test_openChatWindow_on_notification() {
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
waitForCondition(function() opened).then(() => {
waitForCondition(() => opened).then(() => {
do_check_true(opened, "should open a chat window");
do_check_eq(Services.prefs.getCharPref("loop.seenToS"), "seen",

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://services-common/utils.js");
/**

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://services-common/utils.js");

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const FAKE_FXA_TOKEN_DATA = JSON.stringify({
"token_type": "bearer",
"access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
@ -20,7 +22,7 @@ const LOOP_INITIAL_DELAY_PREF = "loop.initialDelay";
* This file is to test restart+reauth.
*/
add_task(function test_initialize_with_no_guest_rooms_and_no_auth_token() {
add_task(function* test_initialize_with_no_guest_rooms_and_no_auth_token() {
// Set time to be 2 seconds in the past.
var nowSeconds = Date.now() / 1000;
Services.prefs.setBoolPref(LOOP_CREATED_ROOM_PREF, false);
@ -34,7 +36,7 @@ add_task(function test_initialize_with_no_guest_rooms_and_no_auth_token() {
});
});
add_task(function test_initialize_with_created_room_and_no_auth_token() {
add_task(function* test_initialize_with_created_room_and_no_auth_token() {
Services.prefs.setBoolPref(LOOP_CREATED_ROOM_PREF, true);
Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
@ -50,7 +52,7 @@ add_task(function test_initialize_with_created_room_and_no_auth_token() {
});
});
add_task(function test_initialize_with_invalid_fxa_token() {
add_task(function* test_initialize_with_invalid_fxa_token() {
Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
@ -83,7 +85,7 @@ add_task(function test_initialize_with_invalid_fxa_token() {
});
});
add_task(function test_initialize_with_fxa_token() {
add_task(function* test_initialize_with_fxa_token() {
Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const LOOP_HAWK_PREF = "loop.hawk-session-token";
const fakeSessionToken1 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1751";
const fakeSessionToken2 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test that things behave reasonably when a reasonable Hawk-Session-Token
* header is returned with the registration response.

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_test(function test_registration_uses_hawk_session_token() {
Services.prefs.setCharPref("loop.hawk-session-token",
"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750");

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// XXX should report error if Hawk-Session-Token is lexically invalid
// (not a string of 64 hex digits) to help resist other possible injection
// attacks. For now, however, we're just checking if it's the right length.

View File

@ -1004,12 +1004,14 @@ let gEditItemOverlay = {
aLastModified, aItemType) {
if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow"))
this._onTagsChange(aItemId);
else if (this._paneInfo.isItem && aProperty == "title")
this._onItemTitleChange(aItemId, aValue);
else (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId)
else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId)
return;
switch (aProperty) {
case "title":
if (this._paneInfo.isItem)
this._onItemTitleChange(aItemId, aValue);
break;
case "uri":
let newURI = NetUtil.newURI(aValue);
if (!newURI.equals(this._paneInfo.uri)) {

View File

@ -1468,6 +1468,12 @@ this.UITour = {
showInfoPanel.bind(this, this._correctAnchor(aAnchor.node)));
},
isInfoOnTarget(aChromeWindow, aTargetName) {
let document = aChromeWindow.document;
let tooltip = document.getElementById("UITourTooltip");
return tooltip.getAttribute("targetName") == aTargetName && tooltip.state != "closed";
},
hideInfo: function(aWindow) {
let document = aWindow.document;

View File

@ -26,6 +26,9 @@ loader.lazyRequireGetter(this, "MarkersOverview",
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
// TODO get rid of retro mode in bug 1160313
loader.lazyRequireGetter(this, "Services");
/**
* For line graphs
*/
@ -165,6 +168,24 @@ const GRAPH_DEFINITIONS = {
}
};
// TODO get rid of retro mode in bug 1160313
const GRAPH_DEFINITIONS_RETRO = {
memory: {
constructor: MemoryGraph,
selector: "#memory-overview",
},
framerate: {
constructor: FramerateGraph,
selector: "#time-framerate",
needsBlueprints: true,
primaryLink: true
},
timeline: {
constructor: TimelineGraph,
selector: "#markers-overview",
}
};
/**
* A controller for orchestrating the performance's tool overview graphs. Constructs,
* syncs, toggles displays and defines the memory, framerate and timeline view.
@ -177,7 +198,9 @@ const GRAPH_DEFINITIONS = {
function GraphsController ({ definition, root, getBlueprint, getTheme }) {
this._graphs = {};
this._enabled = new Set();
this._definition = definition || GRAPH_DEFINITIONS;
// TODO get rid of retro mode in bug 1160313
let RETRO_MODE = Services.prefs.getBoolPref("devtools.performance.ui.retro-mode");
this._definition = definition || (RETRO_MODE ? GRAPH_DEFINITIONS_RETRO : GRAPH_DEFINITIONS);
this._root = root;
this._getBlueprint = getBlueprint;
this._getTheme = getTheme;

View File

@ -284,11 +284,16 @@ let PerformanceController = {
* when the front has started to record.
*/
startRecording: Task.async(function *() {
// Store retro-mode here so we can easily list true/false
// values for reverting.
// TODO bug 1160313
let superMode = !this.getOption("retro-mode");
let options = {
withMarkers: true,
withMemory: this.getOption("enable-memory"),
withMarkers: superMode ? true : false,
withMemory: superMode ? this.getOption("enable-memory") : false,
withTicks: this.getOption("enable-framerate"),
withAllocations: this.getOption("enable-memory"),
withAllocations: superMode ? this.getOption("enable-memory") : false,
allocationsSampleProbability: this.getPref("memory-sample-probability"),
allocationsMaxLogLength: this.getPref("memory-max-log-length"),
bufferSize: this.getPref("profiler-buffer-size"),

View File

@ -122,5 +122,10 @@ support-files =
[browser_profiler_tree-view-06.js]
[browser_profiler_tree-view-07.js]
[browser_profiler_tree-view-08.js]
[browser_timeline_blueprint.js]
[browser_timeline_filters.js]
[browser_timeline-blueprint.js]
[browser_timeline-filters.js]
[browser_timeline-waterfall-background.js]
[browser_timeline-waterfall-generic.js]
[browser_timeline-waterfall-sidebar.js]
# remove in bug 1160313
[browser_retro-test.js]

View File

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that only js-calltree view is on, default, and many things are hidden
* when in retro mode.
*/
const HIDDEN_OPTIONS = ["option-enable-memory", "option-invert-flame-graph", "option-show-jit-optimizations", "option-flatten-tree-recursion"];
Services.prefs.setBoolPref("devtools.performance.ui.retro-mode", true);
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, PerformanceController, $, $$, JsCallTreeView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
let model = PerformanceController.getCurrentRecording();
ok(model.getMemory().length === 0, "model did not record memory.");
ok(model.getTicks().length !== 0, "model did get ticks.");
ok(model.getAllocations().sites.length === 0, "model did get allocation data.");
ok(model.getAllocations().timestamps.length === 0, "model did get allocation data.");
ok(model.getAllocations().frames.length === 0, "model did get allocation data.");
ok(model.getAllocations().counts.length === 0, "model did get allocation data.");
ok(DetailsView.isViewSelected(JsCallTreeView),
"The jscalltree view is selected by default");
for (let option of $$("#performance-options-menupopup > menuitem")) {
if (HIDDEN_OPTIONS.indexOf(option.id) !== -1) {
ok(option.hidden === true, `${option.id} should be hidden.`);
} else {
ok(option.hidden === false, `${option.id} should be visible.`);
}
}
for (let viewbutton of $$("#performance-toolbar-controls-detail-views > toolbarbutton")) {
ok (viewbutton.hidden === true, `${viewbutton.id} should be hidden.`);
}
ok($("#markers-overview").hidden, "markers overview should be hidden.");
ok($("#memory-overview").hidden, "memory overview should be hidden.");
ok(!$("#time-framerate").hidden, "framerate should be shown.");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the waterfall background is a 1px high canvas stretching across
* the container bounds.
*/
function spawnTest () {
let { target, panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView } = panel.panelWin;
yield startRecording(panel);
ok(true, "Recording has started.");
let updated = 0;
OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
ok((yield waitUntil(() => updated > 0)),
"The overview graphs were updated a bunch of times.");
ok((yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length > 0)),
"There are some markers available.");
let rendered = Promise.all([
DetailsView.selectView("waterfall"),
once(WaterfallView, EVENTS.WATERFALL_RENDERED)
]);
yield stopRecording(panel);
ok(true, "Recording has ended.");
yield rendered;
// Test the waterfall background.
let parentWidth = $("#waterfall-view").getBoundingClientRect().width;
let sidebarWidth = $(".waterfall-sidebar").getBoundingClientRect().width;
let detailsWidth = $("#waterfall-details").getBoundingClientRect().width;
let waterfallWidth = WaterfallView.waterfall._waterfallWidth;
is(waterfallWidth, parentWidth - sidebarWidth - detailsWidth,
"The waterfall width is correct.")
ok(WaterfallView.waterfall._canvas,
"A canvas should be created after the recording ended.");
ok(WaterfallView.waterfall._ctx,
"A 2d context should be created after the recording ended.");
is(WaterfallView.waterfall._canvas.width, waterfallWidth,
"The canvas width is correct.");
is(WaterfallView.waterfall._canvas.height, 1,
"The canvas height is correct.");
yield teardown(panel);
finish();
}

View File

@ -5,22 +5,22 @@
* Tests if the waterfall is properly built after finishing a recording.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, $$, EVENTS, TimelineController } = panel.panelWin;
function spawnTest () {
let { target, panel } = yield initPerformance(SIMPLE_URL);
let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
yield TimelineController.toggleRecording();
yield startRecording(panel);
ok(true, "Recording has started.");
let updated = 0;
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
ok((yield waitUntil(() => updated > 0)),
"The overview graphs were updated a bunch of times.");
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
ok((yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length > 0)),
"There are some markers available.");
yield TimelineController.toggleRecording();
yield stopRecording(panel);
ok(true, "Recording has ended.");
// Test the header container.
@ -62,4 +62,6 @@ add_task(function*() {
"Some marker waterfall nodes should have been created.");
ok($$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar").length,
"Some marker color bars should have been created inside the waterfall.");
});
yield teardown(panel);
finish();
}

View File

@ -5,35 +5,34 @@
* Tests if the sidebar is properly updated when a marker is selected.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, $$, EVENTS, TimelineController, TimelineView, TIMELINE_BLUEPRINT} = panel.panelWin;
let { L10N } = devtools.require("devtools/shared/timeline/global");
function spawnTest () {
let { target, panel } = yield initPerformance(SIMPLE_URL);
let { $, $$, EVENTS, PerformanceController, OverviewView } = panel.panelWin;
let { L10N, TIMELINE_BLUEPRINT } = devtools.require("devtools/shared/timeline/global");
yield TimelineController.toggleRecording();
yield startRecording(panel);
ok(true, "Recording has started.");
yield waitUntil(() => {
// Wait until we get 3 different markers.
let markers = TimelineController.getMarkers();
let markers = PerformanceController.getCurrentRecording().getMarkers();
return markers.some(m => m.name == "Styles") &&
markers.some(m => m.name == "Reflow") &&
markers.some(m => m.name == "Paint");
});
yield TimelineController.toggleRecording();
yield stopRecording(panel);
ok(true, "Recording has ended.");
// Select everything
TimelineView.markersOverview.setSelection({ start: 0, end: TimelineView.markersOverview.width })
OverviewView.graphs.get("timeline").setSelection({ start: 0, end: OverviewView.graphs.get("timeline").width })
let bars = $$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar");
let markers = TimelineController.getMarkers();
let markers = PerformanceController.getCurrentRecording().getMarkers();
ok(bars.length > 2, "got at least 3 markers");
let sidebar = $("#timeline-waterfall-details");
let sidebar = $("#waterfall-details");
for (let i = 0; i < bars.length; i++) {
let bar = bars[i];
bar.click();
@ -41,7 +40,7 @@ add_task(function*() {
let name = TIMELINE_BLUEPRINT[m.name].label;
is($("#timeline-waterfall-details .marker-details-type").getAttribute("value"), name,
is($("#waterfall-details .marker-details-type").getAttribute("value"), name,
"sidebar title matches markers name");
let printedStartTime = $(".marker-details-start .marker-details-labelvalue").getAttribute("value");
@ -55,4 +54,6 @@ add_task(function*() {
is(toMs(m.end), printedEndTime, "sidebar end time is valid");
is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
}
});
yield teardown(panel);
finish();
}

View File

@ -56,6 +56,7 @@ let DEFAULT_PREFS = [
"devtools.performance.memory.max-log-length",
"devtools.performance.profiler.buffer-size",
"devtools.performance.profiler.sample-frequency-khz",
"devtools.performance.ui.retro-mode",
].reduce((prefs, pref) => {
prefs[pref] = Preferences.get(pref);
return prefs;
@ -67,6 +68,10 @@ Services.prefs.setBoolPref("devtools.performance.enabled", true);
// be affected by this pref.
Services.prefs.setBoolPref("devtools.debugger.log", false);
// Disable retro mode.
// TODO bug 1160313
Services.prefs.setBoolPref("devtools.performance.ui.retro-mode", false);
/**
* Call manually in tests that use frame script utils after initializing
* the tool. Must be called after initializing so we can detect

View File

@ -71,6 +71,12 @@ let DetailsSubview = {
*/
observedPrefs: [],
/**
* Flag specifying if this view should update while the overview selection
* area is actively being dragged by the mouse.
*/
shouldUpdateWhileMouseIsActive: false,
/**
* Called when recording stops or is selected.
*/
@ -90,7 +96,14 @@ let DetailsSubview = {
*/
_onOverviewRangeChange: function (_, interval) {
if (DetailsView.isViewSelected(this)) {
let debounced = () => this.render(interval);
let debounced = () => {
if (!this.shouldUpdateWhileMouseIsActive && OverviewView.isMouseActive) {
// Don't render yet, while the selection is still being dragged.
setNamedTimeout("range-change-debounce", this.rangeChangeDebounceTime, debounced);
} else {
this.render(interval);
}
};
setNamedTimeout("range-change-debounce", this.rangeChangeDebounceTime, debounced);
} else {
this.shouldUpdateWhenShown = true;

View File

@ -13,7 +13,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
"show-platform-data"
],
rangeChangeDebounceTime: 50, // ms
rangeChangeDebounceTime: 75, // ms
/**
* Sets up the view with event binding.

View File

@ -9,6 +9,8 @@
*/
let JsFlameGraphView = Heritage.extend(DetailsSubview, {
shouldUpdateWhileMouseIsActive: true,
rerenderPrefs: [
"invert-flame-graph",
"flatten-tree-recursion",

View File

@ -9,6 +9,8 @@
*/
let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
shouldUpdateWhileMouseIsActive: true,
rerenderPrefs: [
"invert-flame-graph",
"flatten-tree-recursion",

View File

@ -16,7 +16,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
"hidden-markers"
],
rangeChangeDebounceTime: 10, // ms
rangeChangeDebounceTime: 75, // ms
/**
* Sets up the view with event binding.
@ -74,6 +74,11 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
*/
_onMarkerSelected: function (event, marker) {
let recording = PerformanceController.getCurrentRecording();
// Race condition in tests due to lazy rendering of markers in the
// waterfall? intermittent bug 1157523
if (!recording) {
return;
}
let frames = recording.getFrames();
if (event === "selected") {

View File

@ -94,9 +94,12 @@ let DetailsView = {
let invalidCurrentView = false;
for (let [name, { view }] of Iterator(this.components)) {
let isSupported = this._isViewSupported(name, false);
// TODO bug 1160313 get rid of retro mode checks.
let isRetro = PerformanceController.getOption("retro-mode");
let isSupported = isRetro ? name === "js-calltree" : this._isViewSupported(name, false);
$(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
// TODO bug 1160313 hide all view buttons, but let js-calltree still be "supported"
$(`toolbarbutton[data-view=${name}]`).hidden = isRetro ? true : !isSupported;
// If the view is currently selected and not supported, go back to the
// default view.

View File

@ -92,6 +92,16 @@ let OverviewView = {
yield this.graphs.destroy();
}),
/**
* Returns true if any of the overview graphs have mouse dragging active,
* false otherwise.
*/
get isMouseActive() {
return (this.markersOverview && this.markersOverview.isMouseActive) ||
(this.memoryOverview && this.memoryOverview.isMouseActive) ||
(this.framerateGraph && this.framerateGraph.isMouseActive);
},
/**
* Disabled in the event we're using a Timeline mock, so we'll have no
* timeline, ticks or memory data to show, so just block rendering and hide

View File

@ -21,6 +21,19 @@ let ToolbarView = {
menupopup: $("#performance-options-menupopup")
});
// TODO bug 1160313 get rid of retro mode checks
// hide option buttons here, and any other buttons in the toolbar
// (details.js takes care of view buttons)
if (PerformanceController.getOption("retro-mode")) {
let RETRO_ELEMENTS = [
"#option-flatten-tree-recursion", "#option-enable-memory", "#option-invert-flame-graph",
"#option-show-jit-optimizations", "#filter-button"
];
for (let selector of RETRO_ELEMENTS) {
$(selector).hidden = true;
}
}
yield this.optionsView.initialize();
this.optionsView.on("pref-changed", this._onPrefChanged);

View File

@ -220,6 +220,14 @@ AbstractCanvasGraph.prototype = {
return this._height;
},
/**
* Return true if the mouse is actively messing with the selection, false
* otherwise.
*/
get isMouseActive() {
return this._isMouseActive;
},
/**
* Returns a promise resolved once this graph is ready to receive data.
*/

View File

@ -1,19 +0,0 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
doc_simple-test.html
head.js
[browser_timeline_aaa_run_first_leaktest.js]
[browser_timeline_overview-initial-selection-01.js]
[browser_timeline_overview-initial-selection-02.js]
[browser_timeline_overview-update.js]
[browser_timeline_overview-theme.js]
[browser_timeline_panels.js]
[browser_timeline_recording-without-memory.js]
[browser_timeline_recording.js]
[browser_timeline_waterfall-background.js]
[browser_timeline_waterfall-generic.js]
[browser_timeline_waterfall-styles.js]
[browser_timeline_waterfall-sidebar.js]

View File

@ -1,18 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the timeline leaks on initialization and sudden destruction.
* You can also use this initialization format as a template for other tests.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
ok(target, "Should have a target available.");
ok(panel, "Should have a panel available.");
ok(panel.panelWin.gToolbox, "Should have a toolbox reference on the panel window.");
ok(panel.panelWin.gTarget, "Should have a target reference on the panel window.");
ok(panel.panelWin.gFront, "Should have a front reference on the panel window.");
});

View File

@ -1,44 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the overview has an initial selection when recording has finished
* and there is data available.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
$("#memory-checkbox").checked = true;
yield TimelineController.updateMemoryRecording();
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
let updated = 0;
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
ok((yield waitUntil(() => updated > 10)),
"The overview graph was updated a bunch of times.");
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
"There are some markers available.");
ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
"There are some memory measurements available now.");
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
let interval = TimelineController.getInterval();
let markers = TimelineController.getMarkers();
let selection = TimelineView.markersOverview.getSelection();
is((selection.start) | 0,
(markers[0].start * TimelineView.markersOverview.dataScaleX) | 0,
"The initial selection start is correct.");
is((selection.end - selection.start) | 0,
(selectionRatio * TimelineView.markersOverview.width) | 0,
"The initial selection end is correct.");
});

View File

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the overview has no initial selection when recording has finished
* and there is no data available.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
$("#memory-checkbox").checked = true;
yield TimelineController.updateMemoryRecording();
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
yield TimelineController._stopRecordingAndDiscardData();
ok(true, "Recording has ended.");
let markers = TimelineController.getMarkers();
let memory = TimelineController.getMemory();
let selection = TimelineView.markersOverview.getSelection();
is(markers.length, 0,
"There are no markers available.");
is(memory.length, 0,
"There are no memory measurements available.");
is(selection.start, null,
"The initial selection start is correct.");
is(selection.end, null,
"The initial selection end is correct.");
});

View File

@ -1,84 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the markers and memory overviews render with the correct
* theme on load, and rerenders when changed.
*/
const LIGHT_BG = "#fcfcfc";
const DARK_BG = "#14171a";
add_task(function*() {
let { target, panel } = yield initTimelinePanel("about:blank");
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
$("#memory-checkbox").checked = true;
setTheme("dark");
yield TimelineController.updateMemoryRecording();
is(TimelineView.markersOverview.backgroundColor, DARK_BG,
"correct theme on load for markers.");
is(TimelineView.memoryOverview.backgroundColor, DARK_BG,
"correct theme on load for memory.");
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
let refreshed = Promise.all([
once(TimelineView.markersOverview, "refresh"),
once(TimelineView.memoryOverview, "refresh"),
]);
setTheme("light");
yield refreshed;
ok(true, "Both memory and markers were rerendered after theme change.");
is(TimelineView.markersOverview.backgroundColor, LIGHT_BG,
"correct theme on after toggle for markers.");
is(TimelineView.memoryOverview.backgroundColor, LIGHT_BG,
"correct theme on after toggle for memory.");
refreshed = Promise.all([
once(TimelineView.markersOverview, "refresh"),
once(TimelineView.memoryOverview, "refresh"),
]);
setTheme("dark");
yield refreshed;
ok(true, "Both memory and markers were rerendered after theme change.");
is(TimelineView.markersOverview.backgroundColor, DARK_BG,
"correct theme on after toggle for markers once more.");
is(TimelineView.memoryOverview.backgroundColor, DARK_BG,
"correct theme on after toggle for memory once more.");
refreshed = Promise.all([
once(TimelineView.markersOverview, "refresh"),
once(TimelineView.memoryOverview, "refresh"),
]);
// Set theme back to light
setTheme("light");
yield refreshed;
});
/**
* Mimics selecting the theme selector in the toolbox;
* sets the preference and emits an event on gDevTools to trigger
* the themeing.
*/
function setTheme (newTheme) {
let oldTheme = Services.prefs.getCharPref("devtools.theme");
info("Setting `devtools.theme` to \"" + newTheme + "\"");
Services.prefs.setCharPref("devtools.theme", newTheme);
gDevTools.emit("pref-changed", {
pref: "devtools.theme",
newValue: newTheme,
oldValue: oldTheme
});
}

View File

@ -1,74 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the markers and memory overviews are continuously updated.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel("about:blank");
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
$("#memory-checkbox").checked = true;
yield TimelineController.updateMemoryRecording();
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
ok("selectionEnabled" in TimelineView.markersOverview,
"The selection should not be enabled for the markers overview (1).");
is(TimelineView.markersOverview.selectionEnabled, false,
"The selection should not be enabled for the markers overview (2).");
is(TimelineView.markersOverview.hasSelection(), false,
"The markers overview shouldn't have a selection before recording.");
ok("selectionEnabled" in TimelineView.memoryOverview,
"The selection should not be enabled for the memory overview (1).");
is(TimelineView.memoryOverview.selectionEnabled, false,
"The selection should not be enabled for the memory overview (2).");
is(TimelineView.memoryOverview.hasSelection(), false,
"The memory overview shouldn't have a selection before recording.");
let updated = 0;
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
ok((yield waitUntil(() => updated > 10)),
"The overviews were updated a bunch of times.");
ok((yield waitUntil(() => TimelineController.getMemory().length > 10)),
"There are some memory measurements available now.");
ok("selectionEnabled" in TimelineView.markersOverview,
"The selection should still not be enabled for the markers overview (3).");
is(TimelineView.markersOverview.selectionEnabled, false,
"The selection should still not be enabled for the markers overview (4).");
is(TimelineView.markersOverview.hasSelection(), false,
"The markers overview should not have a selection while recording.");
ok("selectionEnabled" in TimelineView.memoryOverview,
"The selection should still not be enabled for the memory overview (3).");
is(TimelineView.memoryOverview.selectionEnabled, false,
"The selection should still not be enabled for the memory overview (4).");
is(TimelineView.memoryOverview.hasSelection(), false,
"The memory overview should not have a selection while recording.");
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineController.getMarkers().length, 0,
// "There are no markers available.");
isnot(TimelineController.getMemory().length, 0,
"There are some memory measurements available.");
is(TimelineView.markersOverview.selectionEnabled, true,
"The selection should now be enabled for the markers overview.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineView.markersOverview.hasSelection(), false,
// "The markers overview should not have a selection after recording.");
is(TimelineView.memoryOverview.selectionEnabled, true,
"The selection should now be enabled for the memory overview.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineView.memoryOverview.hasSelection(), false,
// "The memory overview should not have a selection after recording.");
});

View File

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the timeline panels are correctly shown and hidden when
* recording starts and stops.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, EVENTS } = panel.panelWin;
is($("#record-button").hasAttribute("checked"), false,
"The record button should not be checked yet.");
is($("#timeline-pane").selectedPanel, $("#empty-notice"),
"An empty notice is initially displayed instead of the waterfall view.");
let whenRecStarted = panel.panelWin.once(EVENTS.RECORDING_STARTED);
EventUtils.synthesizeMouseAtCenter($("#record-button"), {}, panel.panelWin);
yield whenRecStarted;
ok(true, "Recording has started.");
is($("#record-button").getAttribute("checked"), "true",
"The record button should be checked now.");
is($("#timeline-pane").selectedPanel, $("#recording-notice"),
"A recording notice is now displayed instead of the waterfall view.");
let whenRecEnded = panel.panelWin.once(EVENTS.RECORDING_ENDED);
EventUtils.synthesizeMouseAtCenter($("#record-button"), {}, panel.panelWin);
yield whenRecEnded;
ok(true, "Recording has ended.");
is($("#record-button").hasAttribute("checked"), false,
"The record button should be unchecked again.");
is($("#timeline-pane").selectedPanel, $("#timeline-waterfall-container"),
"A waterfall view is now displayed.");
});

View File

@ -1,33 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the timeline actor isn't unnecessarily asked to record memory.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
let updated = 0;
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
ok((yield waitUntil(() => updated > 10)),
"The overview graph was updated a bunch of times.");
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
"There are some markers available.");
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
let markers = TimelineController.getMarkers();
let memory = TimelineController.getMemory();
isnot(markers.length, 0,
"There are some markers available.");
is(memory.length, 0,
"There are no memory measurements available.");
});

View File

@ -1,45 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the timeline can properly start and stop a recording.
*/
add_task(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, gFront, TimelineController } = panel.panelWin;
$("#memory-checkbox").checked = true;
yield TimelineController.updateMemoryRecording();
is((yield gFront.isRecording()), false,
"The timeline actor should not be recording when the tool starts.");
is(TimelineController.getMarkers().length, 0,
"There should be no markers available when the tool starts.");
is(TimelineController.getMemory().length, 0,
"There should be no memory measurements available when the tool starts.");
yield TimelineController.toggleRecording();
is((yield gFront.isRecording()), true,
"The timeline actor should be recording now.");
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
"There are some markers available now.");
ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
"There are some memory measurements available now.");
info("Interval: " + TimelineController.getInterval().toSource());
info("Markers: " + TimelineController.getMarkers().toSource());
info("Memory: " + TimelineController.getMemory().toSource());
ok("startTime" in TimelineController.getInterval(),
"A `startTime` field was set on the recording data.");
ok("endTime" in TimelineController.getInterval(),
"An `endTime` field was set on the recording data.");
ok(TimelineController.getInterval().endTime >
TimelineController.getInterval().startTime,
"Some time has passed since the recording started.");
yield TimelineController.toggleRecording();
});

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