Merge m-c to inbound a=merge

This commit is contained in:
Wes Kocher 2014-11-06 19:08:13 -08:00
commit 6c41020ae8
229 changed files with 4254 additions and 3055 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1087560 changed a mochitest manifest without touching its moz.build.
Bug 1095234 - Bug 1091260 stopped packaging a devtools file with EXTRA_JS_MODULES while making it require pre-processing.

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "1380aa2d9459bc1711cb5504c4c806038107428e",
"revision": "ce1789cc91feafe53596cfd0360cd12f7cc69d3b",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1b974ce130eed3988ff5d012c7bd8431c4aba93b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -1,3 +1,7 @@
. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
ac_add_options --enable-application=b2g/dev
# Include Firefox OS fonts.
# TODO Uncomment after Mulet ships with the fonts (bug 1011562).
#MOZTTDIR=$topsrcdir/moztt

View File

@ -21,4 +21,8 @@ ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1
# Include Firefox OS fonts.
# TODO Uncomment after Mulet ships with the fonts (bug 1011562).
#MOZTTDIR=$topsrcdir/moztt
. "$topsrcdir/build/mozconfig.common.override"

View File

@ -1,3 +1,7 @@
. "$topsrcdir/browser/config/mozconfigs/win32/nightly"
ac_add_options --enable-application=b2g/dev
# Include Firefox OS fonts.
# TODO Uncomment after Mulet ships with the fonts (bug 1011562).
#MOZTTDIR=$topsrcdir/moztt

View File

@ -0,0 +1,27 @@
[
{
"size": 82,
"digest": "70a6126249e40aa1da32248bf6bfe45e0d8c87334579ec0cf69403e61b635e27c766d9bf08d530978286552f158ee24c74b0168a57cc6b734dcfed4fc5e09cff",
"algorithm": "sha512",
"filename": "setup.sh"
},
{
"size": 80458572,
"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
"algorithm": "sha512",
"filename": "gcc.tar.xz"
},
{
"size": 168320,
"digest": "c0f4a2da0b07ca6fc69290fbc5ed68f56c6b1ba4d593b220fd49b14ac4885e6ec949e695fd9a7ac464e0e86b652e99f6bd4af849fec072264b29a8f9686d2fc4",
"algorithm": "sha512",
"filename": "sccache.tar.bz2"
},
{
"size": 31057326,
"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
"algorithm": "sha512",
"filename": "moztt.tar.bz2",
"unpack": "True"
}
]

View File

@ -0,0 +1,30 @@
[
{
"clang_version": "r183744"
},
{
"size": 88,
"digest": "0d2ae9bcd7cea34ec0b768270725e98410dbb3bc150c7381e0dcf3eb5dbb3e69ac76dbb0f46b056151d6a6fa8681cab06da68173ae8598f3397b8f7628e67381",
"algorithm": "sha512",
"filename": "setup.sh"
},
{
"size": 59602619,
"digest": "86662ebc0ef650490559005948c4f0cb015dad72c7cac43732c2bf2995247081e30c139cf8008d19670a0009fc302c4eee2676981ee3f9ff4a15c01af22b783b",
"algorithm": "sha512",
"filename": "clang.tar.bz2"
},
{
"size": 168320,
"digest": "c0f4a2da0b07ca6fc69290fbc5ed68f56c6b1ba4d593b220fd49b14ac4885e6ec949e695fd9a7ac464e0e86b652e99f6bd4af849fec072264b29a8f9686d2fc4",
"algorithm": "sha512",
"filename": "sccache.tar.bz2"
},
{
"size": 31057326,
"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
"algorithm": "sha512",
"filename": "moztt.tar.bz2",
"unpack": "True"
}
]

View File

@ -0,0 +1,27 @@
[
{
"size": 266240,
"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
"algorithm": "sha512",
"filename": "mozmake.exe"
},
{
"size": 51,
"digest": "c8e40edb314eeabfb92c77cf5ff9a7857033f15dd65a00349bcf9e3e5b75624afc71f733b2ff7e029c20a78313038409c2bd022bf7e5a7e0c487fc2c2d640986",
"algorithm": "sha512",
"filename": "setup.sh"
},
{
"size": 168320,
"digest": "c0f4a2da0b07ca6fc69290fbc5ed68f56c6b1ba4d593b220fd49b14ac4885e6ec949e695fd9a7ac464e0e86b652e99f6bd4af849fec072264b29a8f9686d2fc4",
"algorithm": "sha512",
"filename": "sccache.tar.bz2"
},
{
"size": 31057326,
"digest": "b844c3e52be493d2cacafa58c4a924b89c9be8d2dcc2a7c71aed58c253d8035fba4d51df309f73e3c4342a1f3c3898a9a25c4815e2112888d1280f43c41c8e51",
"algorithm": "sha512",
"filename": "moztt.tar.bz2",
"unpack": "True"
}
]

View File

@ -1650,7 +1650,7 @@ pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
pref("loop.legal.ToS_url", "https://hello.firefox.com/legal/terms/");
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/");
pref("loop.do_not_disturb", false);
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/Firefox-Long.ogg");
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
pref("loop.retry_delay.start", 60000);
pref("loop.retry_delay.limit", 300000);
pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
@ -1660,9 +1660,9 @@ pref("loop.debug.dispatcher", false);
pref("loop.debug.websocket", false);
pref("loop.debug.sdk", false);
#ifdef DEBUG
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*");
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
#else
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net");
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
#endif
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");

View File

@ -787,9 +787,6 @@ statuspanel[inactive][previoustype=overLink] {
max-width: 32em;
}
/* highlighter */
%include highlighter.css
/* gcli */
html|*#gcli-tooltip-frame,

View File

@ -4551,15 +4551,14 @@ function nsBrowserAccess() { }
nsBrowserAccess.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
_openURIInNewTab: function(aURI, aOpener, aIsExternal) {
_openURIInNewTab: function(aURI, aReferrer, aIsPrivate, aIsExternal) {
let win, needToFocusWin;
// try the current window. if we're in a popup, fall back on the most recent browser window
if (window.toolbar.visible)
win = window;
else {
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});
win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
needToFocusWin = true;
}
@ -4575,10 +4574,9 @@ nsBrowserAccess.prototype = {
}
let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
let referrer = aOpener ? makeURI(aOpener.location.href) : null;
let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
referrerURI: referrer,
referrerURI: aReferrer,
fromExternal: aIsExternal,
inBackground: loadInBackground});
let browser = win.gBrowser.getBrowserForTab(tab);
@ -4615,7 +4613,9 @@ nsBrowserAccess.prototype = {
newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
break;
case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
let browser = this._openURIInNewTab(aURI, aOpener, isExternal);
let referrer = aOpener ? makeURI(aOpener.location.href) : null;
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
let browser = this._openURIInNewTab(aURI, referrer, isPrivate, isExternal);
if (browser)
newWindow = browser.contentWindow;
break;
@ -4634,14 +4634,14 @@ nsBrowserAccess.prototype = {
return newWindow;
},
openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
dump("Error: openURIInFrame can only open in new tabs");
return null;
}
var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
let browser = this._openURIInNewTab(aURI, aOpener, isExternal);
let browser = this._openURIInNewTab(aURI, aParams.referrer, aParams.isPrivate, isExternal);
if (browser)
return browser.QueryInterface(Ci.nsIFrameLoaderOwner);

View File

@ -123,7 +123,7 @@ chatBrowserAccess.prototype = {
return browser ? browser.contentWindow : null;
},
openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
let browser = this._openURIInNewTab(aURI, aWhere);
return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
},

View File

@ -1,80 +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/. */
.highlighter-container {
pointer-events: none;
}
/*
* Box model highlighter
*/
svg|svg.box-model-root[hidden],
svg|line.box-model-guide-top[hidden],
svg|line.box-model-guide-right[hidden],
svg|line.box-model-guide-left[hidden],
svg|line.box-model-guide-bottom[hidden] {
display: none;
}
/*
* Node Infobar
*/
.highlighter-nodeinfobar-container {
position: relative;
}
.highlighter-nodeinfobar-positioner {
position: absolute;
max-width: 95%;
}
.highlighter-nodeinfobar-positioner[hidden] {
opacity: 0;
pointer-events: none;
display: -moz-box;
}
.highlighter-nodeinfobar-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
direction: ltr;
}
html|*.highlighter-nodeinfobar-id,
html|*.highlighter-nodeinfobar-classes,
html|*.highlighter-nodeinfobar-pseudo-classes,
html|*.highlighter-nodeinfobar-dimensions,
html|*.highlighter-nodeinfobar-tagname {
-moz-user-select: text;
-moz-user-focus: normal;
cursor: text;
}
.highlighter-nodeinfobar-arrow {
display: none;
}
.highlighter-nodeinfobar-positioner[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
display: block;
}
.highlighter-nodeinfobar-positioner[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
display: block;
}
.highlighter-nodeinfobar-positioner[disabled] {
visibility: hidden;
}
html|*.highlighter-nodeinfobar-tagname {
text-transform: lowercase;
}
/*
* Css transform highlighter
*/
svg|svg.css-transform-root[hidden] {
display: none;
}

View File

@ -74,7 +74,7 @@ Sanitizer.prototype = {
itemsToClear.splice(openWindowsIndex, 1);
let item = this.items.openWindows;
function onWindowsCleaned() {
let ok = item.clear(() => {
try {
let clearedPromise = this.sanitize(itemsToClear);
clearedPromise.then(deferred.resolve, deferred.reject);
@ -83,9 +83,7 @@ Sanitizer.prototype = {
Cu.reportError(error);
deferred.reject(error);
}
}
let ok = item.clear(onWindowsCleaned.bind(this));
});
// When cancelled, reject immediately
if (!ok) {
deferred.reject("Sanitizer canceled closing windows");

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/. */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
let chatbar = document.getElementById("pinnedchats");
add_chat_task(function* testOpenCloseChat() {

View File

@ -4,6 +4,7 @@
// Tests the focus functionality.
Components.utils.import("resource://gre/modules/Promise.jsm", this);
const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
// Is the currently opened tab focused?

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/. */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
let chatbar = document.getElementById("pinnedchats");
function promiseNewWindowLoaded() {

View File

@ -300,11 +300,11 @@ skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and
skip-if = os == 'win' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug1064280_changeUrlInPinnedTab.js]
[browser_canonizeURL.js]
skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
[browser_contentAreaClick.js]
skip-if = e10s
[browser_contextSearchTabPosition.js]
skip-if = os == "mac" || e10s # bug 967013, bug 926729
skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
[browser_ctrlTab.js]
skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
[browser_customize_popupNotification.js]

View File

@ -49,6 +49,11 @@ let check_history = Task.async(function*() {
}
});
function clear_history() {
gExpectedHistory.index = -1;
gExpectedHistory.entries = [];
}
// Waits for a load and updates the known history
let waitForLoad = Task.async(function*(uri) {
info("Loading " + uri);
@ -63,6 +68,28 @@ let waitForLoad = Task.async(function*(uri) {
});
});
// Waits for a load and updates the known history
let waitForLoadWithFlags = Task.async(function*(uri, flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE) {
info("Loading " + uri + " flags = " + flags);
gBrowser.selectedBrowser.loadURIWithFlags(uri, flags, null, null, null);
yield waitForDocLoadComplete();
if (!(flags & Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY)) {
if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY) {
gExpectedHistory.entries.pop();
}
else {
gExpectedHistory.index++;
}
gExpectedHistory.entries.push({
uri: gBrowser.currentURI.spec,
title: gBrowser.contentTitle
});
}
});
let back = Task.async(function*() {
info("Going back");
gBrowser.goBack();
@ -80,10 +107,7 @@ let forward = Task.async(function*() {
// Tests that navigating from a page that should be in the remote process and
// a page that should be in the main process works and retains history
add_task(function* test_navigation() {
SimpleTest.requestCompleteLog();
let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
let expectedRemote = remoting ? "true" : "";
let expectedRemote = gMultiProcessBrowser ? "true" : "";
info("1");
// Create a tab and load a remote page in it
@ -154,13 +178,13 @@ add_task(function* test_navigation() {
info("11");
gBrowser.removeCurrentTab();
clear_history();
});
// Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a
// different process updates the browser synchronously
add_task(function* test_synchronous() {
let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
let expectedRemote = remoting ? "true" : "";
let expectedRemote = gMultiProcessBrowser ? "true" : "";
info("1");
// Create a tab and load a remote page in it
@ -194,4 +218,42 @@ add_task(function* test_synchronous() {
info("4");
gBrowser.removeCurrentTab();
clear_history();
});
// Tests that load flags are correctly passed through to the child process with
// normal loads
add_task(function* test_loadflags() {
let expectedRemote = gMultiProcessBrowser ? "true" : "";
info("1");
// Create a tab and load a remote page in it
gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
yield waitForLoadWithFlags("about:robots");
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
yield check_history();
info("2");
// Load a page in the remote process with some custom flags
yield waitForLoadWithFlags("http://example.com/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY);
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
yield check_history();
info("3");
// Load a non-remote page
yield waitForLoadWithFlags("about:robots");
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
yield check_history();
info("4");
// Load another remote page
yield waitForLoadWithFlags("http://example.org/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY);
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
yield check_history();
is(gExpectedHistory.entries.length, 2, "Should end with the right number of history entries");
info("5");
gBrowser.removeCurrentTab();
clear_history();
});

View File

@ -10,11 +10,20 @@
* ALL need to match an error in order for that error not to cause a test
* failure. */
const kWhitelist = [
{sourceName: /cleopatra.*(tree|ui)\.css/i}, /* Cleopatra is imported as-is, see bug 1004421 */
{sourceName: /codemirror\.css/i}, /* CodeMirror is imported as-is, see bug 1004423 */
{sourceName: /web\/viewer\.css/i, errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i }, /* PDFjs is futureproofing its pseudoselectors, and those rules are dropped. */
{sourceName: /aboutaccounts\/(main|normalize)\.css/i}, /* Tracked in bug 1004428 */
{sourceName: /loop\/.*sdk-content\/.*\.css$/i /* TokBox SDK assets, see bug 1032469 */}
// Cleopatra is imported as-is, see bug 1004421.
{sourceName: /cleopatra.*(tree|ui)\.css/i},
// CodeMirror is imported as-is, see bug 1004423.
{sourceName: /codemirror\.css/i},
// PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
{sourceName: /web\/viewer\.css/i,
errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i},
// Tracked in bug 1004428.
{sourceName: /aboutaccounts\/(main|normalize)\.css/i},
// TokBox SDK assets, see bug 1032469.
{sourceName: /loop\/.*sdk-content\/.*\.css$/i},
// Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
{sourceName: /highlighter\.css/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i}
];
let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");

View File

@ -27,7 +27,8 @@ add_task(function testWrapUnwrap() {
// Creating and destroying a widget should correctly deal with panel placeholders
add_task(function testPanelPlaceholders() {
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 1 : 2, "The number of placeholders should be correct.");
let expectedPlaceholders = (isInWin8() ? 1 : 2) + (isInDevEdition() ? 1 : 0);
is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
let elem = document.getElementById(kTestWidget2);
let wrapper = document.getElementById("wrapper-" + kTestWidget2);
@ -35,7 +36,8 @@ add_task(function testPanelPlaceholders() {
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
is(wrapper.parentNode, panel, "Wrapper should be in panel");
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 3 : 1, "The number of placeholders should be correct.");
expectedPlaceholders = (isInWin8() ? 3 : 1) + (isInDevEdition() ? 1 : 0);
is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
CustomizableUI.destroyWidget(kTestWidget2);
wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(!wrapper, "There should be a wrapper");

View File

@ -18,6 +18,9 @@ XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
// The maximum number of clients that we support currently.
const CLIENT_MAX_SIZE = 2;
const roomsPushNotification = function(version, channelID) {
return LoopRoomsInternal.onNotification(version, channelID);
};
@ -271,6 +274,81 @@ let LoopRoomsInternal = {
}, error => callback(error)).catch(error => callback(error));
},
/**
* Internal function to handle POSTs to a room.
*
* @param {String} roomToken The room token.
* @param {Object} postData The data to post to the room.
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`.
*/
_postToRoom(roomToken, postData, callback) {
let url = "/rooms/" + encodeURIComponent(roomToken);
MozLoopService.hawkRequest(this.sessionType, url, "POST", postData).then(response => {
// Delete doesn't have a body return.
var joinData = response.body ? JSON.parse(response.body) : {};
callback(null, joinData);
}, error => callback(error)).catch(error => callback(error));
},
/**
* Joins a room
*
* @param {String} roomToken The room token.
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`.
*/
join: function(roomToken, callback) {
this._postToRoom(roomToken, {
action: "join",
displayName: MozLoopService.userProfile.email,
clientMaxSize: CLIENT_MAX_SIZE
}, callback);
},
/**
* Refreshes a room
*
* @param {String} roomToken The room token.
* @param {String} sessionToken The session token for the session that has been
* joined
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`.
*/
refreshMembership: function(roomToken, sessionToken, callback) {
this._postToRoom(roomToken, {
action: "refresh",
sessionToken: sessionToken
}, callback);
},
/**
* Leaves a room. Although this is an sync function, no data is returned
* from the server.
*
* @param {String} roomToken The room token.
* @param {String} sessionToken The session token for the session that has been
* joined
* @param {Function} callback Optional. Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`.
*/
leave: function(roomToken, sessionToken, callback) {
if (!callback) {
callback = function(error) {
if (error) {
MozLoopService.log.error(error);
}
};
}
this._postToRoom(roomToken, {
action: "leave",
sessionToken: sessionToken
}, callback);
},
/**
* Callback used to indicate changes to rooms data on the LoopServer.
@ -322,6 +400,19 @@ this.LoopRooms = {
return LoopRoomsInternal.delete(roomToken, callback);
},
join: function(roomToken, callback) {
return LoopRoomsInternal.join(roomToken, callback);
},
refreshMembership: function(roomToken, sessionToken, callback) {
return LoopRoomsInternal.refreshMembership(roomToken, sessionToken,
callback);
},
leave: function(roomToken, sessionToken, callback) {
return LoopRoomsInternal.leave(roomToken, sessionToken, callback);
},
promise: function(method, ...params) {
return new Promise((resolve, reject) => {
this[method](...params, (error, result) => {

View File

@ -13,6 +13,7 @@ Cu.import("resource:///modules/loop/LoopCalls.jsm");
Cu.import("resource:///modules/loop/MozLoopService.jsm");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/loop/LoopContacts.jsm");
Cu.importGlobalProperties(["Blob"]);
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
"resource:///modules/loop/LoopContacts.jsm");
@ -685,6 +686,31 @@ function injectLoopAPI(targetWindow) {
return MozLoopService.generateUUID();
}
},
getAudioBlob: {
enumerable: true,
writable: true,
value: function(name, callback) {
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
let url = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
request.open("GET", url, true);
request.responseType = "arraybuffer";
request.onload = () => {
if (request.status < 200 || request.status >= 300) {
let error = new Error(request.status + " " + request.statusText);
callback(cloneValueInto(error, targetWindow));
return;
}
let blob = new Blob([request.response], {type: "audio/ogg"});
callback(null, cloneValueInto(blob, targetWindow));
};
request.send();
}
}
};
function onStatusChanged(aSubject, aTopic, aData) {

View File

@ -21,7 +21,7 @@ loop.conversation = (function(mozL10n) {
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
mixins: [sharedMixins.DropdownMenuMixin],
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
@ -185,10 +185,16 @@ loop.conversation = (function(mozL10n) {
* incoming call views (bug 1088672).
*/
var GenericFailureView = React.createClass({displayName: 'GenericFailureView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("failure");
},
render: function() {
document.title = mozL10n.get("generic_failure_title");
@ -665,7 +671,11 @@ loop.conversation = (function(mozL10n) {
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
// XXX Move to the conversation models, when we transition
// incoming calls to flux (bug 1088672).
navigator.mozLoop.calls.clearCallInProgress(windowId);
dispatcher.dispatch(new sharedActions.WindowUnload());
});
React.renderComponent(AppControllerView({

View File

@ -21,7 +21,7 @@ loop.conversation = (function(mozL10n) {
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({
mixins: [sharedMixins.DropdownMenuMixin],
mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
@ -185,10 +185,16 @@ loop.conversation = (function(mozL10n) {
* incoming call views (bug 1088672).
*/
var GenericFailureView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("failure");
},
render: function() {
document.title = mozL10n.get("generic_failure_title");
@ -665,7 +671,11 @@ loop.conversation = (function(mozL10n) {
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
// XXX Move to the conversation models, when we transition
// incoming calls to flux (bug 1088672).
navigator.mozLoop.calls.clearCallInProgress(windowId);
dispatcher.dispatch(new sharedActions.WindowUnload());
});
React.renderComponent(<AppControllerView

View File

@ -14,6 +14,7 @@ loop.conversationViews = (function(mozL10n) {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -133,6 +134,8 @@ loop.conversationViews = (function(mozL10n) {
* pending/ringing strings.
*/
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
@ -146,6 +149,10 @@ loop.conversationViews = (function(mozL10n) {
};
},
componentDidMount: function() {
this.play("ringtone", {loop: true});
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
@ -186,7 +193,7 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({displayName: 'CallFailedView',
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -205,6 +212,7 @@ loop.conversationViews = (function(mozL10n) {
},
componentDidMount: function() {
this.play("failure");
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
this.listenTo(this.props.store, "error:emailLink",

View File

@ -14,6 +14,7 @@ loop.conversationViews = (function(mozL10n) {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
@ -133,6 +134,8 @@ loop.conversationViews = (function(mozL10n) {
* pending/ringing strings.
*/
var PendingConversationView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
@ -146,6 +149,10 @@ loop.conversationViews = (function(mozL10n) {
};
},
componentDidMount: function() {
this.play("ringtone", {loop: true});
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
@ -186,7 +193,7 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.AudioMixin],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -205,6 +212,7 @@ loop.conversationViews = (function(mozL10n) {
},
componentDidMount: function() {
this.play("failure");
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
this.listenTo(this.props.store, "error:emailLink",

View File

@ -10,6 +10,8 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var ROOM_STATES = loop.store.ROOM_STATES;
var DesktopRoomView = React.createClass({displayName: 'DesktopRoomView',
mixins: [Backbone.Events, loop.shared.mixins.DocumentTitleMixin],
@ -19,7 +21,7 @@ loop.roomViews = (function(mozL10n) {
},
getInitialState: function() {
return this.props.roomStore.getStoreState();
return this.props.roomStore.getStoreState("activeRoom");
},
componentWillMount: function() {
@ -41,13 +43,28 @@ loop.roomViews = (function(mozL10n) {
this.stopListening(this.props.roomStore);
},
/**
* Closes the window if the cancel button is pressed in the generic failure view.
*/
closeWindow: function() {
window.close();
},
render: function() {
if (this.state.serverData && this.state.serverData.roomName) {
this.setTitle(this.state.serverData.roomName);
if (this.state.roomName) {
this.setTitle(this.state.roomName);
}
if (this.state.roomState === ROOM_STATES.FAILED) {
return (loop.conversation.GenericFailureView({
cancelCall: this.closeWindow}
));
}
return (
React.DOM.div({className: "goat"})
React.DOM.div(null,
React.DOM.div(null, mozL10n.get("invite_header_text"))
)
);
}
});

View File

@ -10,6 +10,8 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
var ROOM_STATES = loop.store.ROOM_STATES;
var DesktopRoomView = React.createClass({
mixins: [Backbone.Events, loop.shared.mixins.DocumentTitleMixin],
@ -19,7 +21,7 @@ loop.roomViews = (function(mozL10n) {
},
getInitialState: function() {
return this.props.roomStore.getStoreState();
return this.props.roomStore.getStoreState("activeRoom");
},
componentWillMount: function() {
@ -41,13 +43,28 @@ loop.roomViews = (function(mozL10n) {
this.stopListening(this.props.roomStore);
},
/**
* Closes the window if the cancel button is pressed in the generic failure view.
*/
closeWindow: function() {
window.close();
},
render: function() {
if (this.state.serverData && this.state.serverData.roomName) {
this.setTitle(this.state.serverData.roomName);
if (this.state.roomName) {
this.setTitle(this.state.roomName);
}
if (this.state.roomState === ROOM_STATES.FAILED) {
return (<loop.conversation.GenericFailureView
cancelCall={this.closeWindow}
/>);
}
return (
<div className="goat"/>
<div>
<div>{mozL10n.get("invite_header_text")}</div>
</div>
);
}
});

View File

@ -67,6 +67,12 @@ loop.shared.actions = (function() {
windowType: String
}),
/**
* Used to signal when the window is being unloaded.
*/
WindowUnload: Action.define("windowUnload", {
}),
/**
* Fetch a new call url from the server, intended to be sent over email when
* a contact can't be reached.
@ -227,6 +233,46 @@ loop.shared.actions = (function() {
*/
CopyRoomUrl: Action.define("copyRoomUrl", {
roomUrl: String
}),
/**
* XXX: should move to some roomActions module - refs bug 1079284
*/
RoomFailure: Action.define("roomFailure", {
error: Object
}),
/**
* Updates the room information when it is received.
* XXX: should move to some roomActions module - refs bug 1079284
*
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
*/
UpdateRoomInfo: Action.define("updateRoomInfo", {
roomName: String,
roomOwner: String,
roomToken: String,
roomUrl: String
}),
/**
* Starts the process for the user to join the room.
* XXX: should move to some roomActions module - refs bug 1079284
*/
JoinRoom: Action.define("joinRoom", {
}),
/**
* Signals the user has successfully joined the room on the loop-server.
* XXX: should move to some roomActions module - refs bug 1079284
*
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#Joining_a_Room
*/
JoinedRoom: Action.define("joinedRoom", {
apiKey: String,
sessionToken: String,
sessionId: String,
expires: Number
})
};
})();

View File

@ -11,6 +11,19 @@ loop.store.ActiveRoomStore = (function() {
var sharedActions = loop.shared.actions;
var ROOM_STATES = loop.store.ROOM_STATES = {
// The initial state of the room
INIT: "room-init",
// The store is gathering the room data
GATHER: "room-gather",
// The store has got the room data
READY: "room-ready",
// The room is known to be joined on the loop-server
JOINED: "room-joined",
// There was an issue with the room
FAILED: "room-failed"
};
/**
* Store for things that are local to this instance (in this profile, on
* this machine) of this roomRoom store, in addition to a mirror of some
@ -29,53 +42,78 @@ loop.store.ActiveRoomStore = (function() {
if (!options.dispatcher) {
throw new Error("Missing option dispatcher");
}
this.dispatcher = options.dispatcher;
this._dispatcher = options.dispatcher;
if (!options.mozLoop) {
throw new Error("Missing option mozLoop");
}
this.mozLoop = options.mozLoop;
this._mozLoop = options.mozLoop;
this.dispatcher.register(this, [
"setupWindowData"
this._dispatcher.register(this, [
"roomFailure",
"setupWindowData",
"updateRoomInfo",
"joinRoom",
"joinedRoom",
"windowUnload"
]);
}
ActiveRoomStore.prototype = _.extend({
/**
* Stored data reflecting the local state of a given room, used to drive
* the room's views.
*
* @property {Object} serverData - local cache of the data returned by
* MozLoop.getRoomData for this room.
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
* for the main data. Additional properties below.
*
* @property {ROOM_STATES} roomState - the state of the room.
* @property {Error=} error - if the room is an error state, this will be
* set to an Error object reflecting the problem;
* otherwise it will be unset.
*/
_storeState: {
},
this._storeState = {
roomState: ROOM_STATES.INIT
};
}
ActiveRoomStore.prototype = _.extend({
/**
* The time factor to adjust the expires time to ensure that we send a refresh
* before the expiry. Currently set as 90%.
*/
expiresTimeFactor: 0.9,
getStoreState: function() {
return this._storeState;
},
setStoreState: function(state) {
this._storeState = state;
setStoreState: function(newState) {
for (var key in newState) {
this._storeState[key] = newState[key];
}
this.trigger("change");
},
/**
* Execute setupWindowData event action from the dispatcher. This primes
* the store with the roomToken, and calls MozLoop.getRoomData on that
* ID. This will return either a reflection of state on the server, or,
* if the createRoom call hasn't yet returned, it will have at least the
* roomName as specified to the createRoom method.
* Handles a room failure. Currently this prints the error to the console
* and sets the roomState to failed.
*
* When the room name gets set, that will trigger the view to display
* that name.
* @param {sharedActions.RoomFailure} actionData
*/
roomFailure: function(actionData) {
console.error("Error in state `" + this._storeState.roomState + "`:",
actionData.error);
this.setStoreState({
error: actionData.error,
roomState: ROOM_STATES.FAILED
});
},
/**
* Execute setupWindowData event action from the dispatcher. This gets
* the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
* It also dispatches JoinRoom as this action is only applicable to the desktop
* client, and needs to auto-join.
*
* @param {sharedActions.SetupWindowData} actionData
*/
@ -85,14 +123,144 @@ loop.store.ActiveRoomStore = (function() {
return;
}
this.mozLoop.rooms.get(actionData.roomToken,
this.setStoreState({
roomState: ROOM_STATES.GATHER
});
// Get the window data from the mozLoop api.
this._mozLoop.rooms.get(actionData.roomToken,
function(error, roomData) {
this.setStoreState({
error: error,
if (error) {
this._dispatcher.dispatch(new sharedActions.RoomFailure({
error: error
}));
return;
}
this._dispatcher.dispatch(
new sharedActions.UpdateRoomInfo({
roomToken: actionData.roomToken,
serverData: roomData
});
roomName: roomData.roomName,
roomOwner: roomData.roomOwner,
roomUrl: roomData.roomUrl
}));
// For the conversation window, we need to automatically
// join the room.
this._dispatcher.dispatch(new sharedActions.JoinRoom());
}.bind(this));
},
/**
* Handles the updateRoomInfo action. Updates the room data and
* sets the state to `READY`.
*
* @param {sharedActions.UpdateRoomInfo} actionData
*/
updateRoomInfo: function(actionData) {
this.setStoreState({
roomName: actionData.roomName,
roomOwner: actionData.roomOwner,
roomState: ROOM_STATES.READY,
roomToken: actionData.roomToken,
roomUrl: actionData.roomUrl
});
},
/**
* Handles the action to join to a room.
*/
joinRoom: function() {
this._mozLoop.rooms.join(this._storeState.roomToken,
function(error, responseData) {
if (error) {
this._dispatcher.dispatch(
new sharedActions.RoomFailure({error: error}));
return;
}
this._dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: responseData.apiKey,
sessionToken: responseData.sessionToken,
sessionId: responseData.sessionId,
expires: responseData.expires
}));
}.bind(this));
},
/**
* Handles the data received from joining a room. It stores the relevant
* data, and sets up the refresh timeout for ensuring membership of the room
* is refreshed regularly.
*
* @param {sharedActions.JoinedRoom} actionData
*/
joinedRoom: function(actionData) {
this.setStoreState({
apiKey: actionData.apiKey,
sessionToken: actionData.sessionToken,
sessionId: actionData.sessionId,
roomState: ROOM_STATES.JOINED
});
this._setRefreshTimeout(actionData.expires);
},
/**
* Handles the window being unloaded. Ensures the room is left.
*/
windowUnload: function() {
this._leaveRoom();
},
/**
* Handles setting of the refresh timeout callback.
*
* @param {Integer} expireTime The time until expiry (in seconds).
*/
_setRefreshTimeout: function(expireTime) {
this._timeout = setTimeout(this._refreshMembership.bind(this),
expireTime * this.expiresTimeFactor * 1000);
},
/**
* Refreshes the membership of the room with the server, and then
* sets up the refresh for the next cycle.
*/
_refreshMembership: function() {
this._mozLoop.rooms.refreshMembership(this._storeState.roomToken,
this._storeState.sessionToken,
function(error, responseData) {
if (error) {
this._dispatcher.dispatch(
new sharedActions.RoomFailure({error: error}));
return;
}
this._setRefreshTimeout(responseData.expires);
}.bind(this));
},
/**
* Handles leaving a room. Clears any membership timeouts, then
* signals to the server the leave of the room.
*/
_leaveRoom: function() {
if (this._storeState.roomState !== ROOM_STATES.JOINED) {
return;
}
if (this._timeout) {
clearTimeout(this._timeout);
delete this._timeout;
}
this._mozLoop.rooms.leave(this._storeState.roomToken,
this._storeState.sessionToken);
this.setStoreState({
roomState: ROOM_STATES.READY
});
}
}, Backbone.Events);

View File

@ -141,6 +141,7 @@ loop.shared.mixins = (function() {
*/
var AudioMixin = {
audio: null,
_audioRequest: null,
_isLoopDesktop: function() {
return typeof rootObject.navigator.mozLoop === "object";
@ -149,27 +150,62 @@ loop.shared.mixins = (function() {
/**
* Starts playing an audio file, stopping any audio that is already in progress.
*
* @param {String} filename The filename to play (excluding the extension).
* @param {String} name The filename to play (excluding the extension).
*/
play: function(filename, options) {
if (this._isLoopDesktop()) {
// XXX: We need navigator.mozLoop.playSound(name), see Bug 1089585.
return;
}
play: function(name, options) {
options = options || {};
options.loop = options.loop || false;
this._ensureAudioStopped();
this.audio = new Audio('shared/sounds/' + filename + ".ogg");
this.audio.loop = options.loop;
this.audio.play();
this._getAudioBlob(name, function(error, blob) {
if (error) {
console.error(error);
return;
}
var url = URL.createObjectURL(blob);
this.audio = new Audio(url);
this.audio.loop = options.loop;
this.audio.play();
}.bind(this));
},
_getAudioBlob: function(name, callback) {
if (this._isLoopDesktop()) {
rootObject.navigator.mozLoop.getAudioBlob(name, callback);
return;
}
var url = "shared/sounds/" + name + ".ogg";
this._audioRequest = new XMLHttpRequest();
this._audioRequest.open("GET", url, true);
this._audioRequest.responseType = "arraybuffer";
this._audioRequest.onload = function() {
var request = this._audioRequest;
var error;
if (request.status < 200 || request.status >= 300) {
error = new Error(request.status + " " + request.statusText);
callback(error);
return;
}
var type = request.getResponseHeader("Content-Type");
var blob = new Blob([request.response], {type: type});
callback(null, blob);
}.bind(this);
this._audioRequest.send(null);
},
/**
* Ensures audio is stopped playing, and removes the object from memory.
*/
_ensureAudioStopped: function() {
if (this._audioRequest) {
this._audioRequest.abort();
delete this._audioRequest;
}
if (this.audio) {
this.audio.pause();
this.audio.removeAttribute("src");

View File

@ -99,10 +99,10 @@ loop.store = loop.store || {};
maxRoomCreationSize: 2,
/**
* The number of hours for which the room will exist.
* The number of hours for which the room will exist - default 8 weeks
* @type {Number}
*/
defaultExpiresIn: 5,
defaultExpiresIn: 24 * 7 * 8,
/**
* Internal store state representation.

View File

@ -540,6 +540,8 @@ loop.shared.views = (function(_, OT, l10n) {
* Feedback view.
*/
var FeedbackView = React.createClass({displayName: 'FeedbackView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
@ -556,6 +558,10 @@ loop.shared.views = (function(_, OT, l10n) {
return {step: "start"};
},
componentDidMount: function() {
this.play("terminated");
},
reset: function() {
this.setState(this.getInitialState());
},

View File

@ -540,6 +540,8 @@ loop.shared.views = (function(_, OT, l10n) {
* Feedback view.
*/
var FeedbackView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
@ -556,6 +558,10 @@ loop.shared.views = (function(_, OT, l10n) {
return {step: "start"};
},
componentDidMount: function() {
this.play("terminated");
},
reset: function() {
this.setState(this.getInitialState());
},

View File

@ -81,7 +81,11 @@ browser.jar:
content/browser/loop/shared/libs/backbone-1.1.2.js (content/shared/libs/backbone-1.1.2.js)
# Shared sounds
content/browser/loop/shared/sounds/Firefox-Long.ogg (content/shared/sounds/Firefox-Long.ogg)
content/browser/loop/shared/sounds/ringtone.ogg (content/shared/sounds/ringtone.ogg)
content/browser/loop/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
content/browser/loop/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
content/browser/loop/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
content/browser/loop/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
# Partner SDK assets
content/browser/loop/libs/sdk.js (content/shared/libs/sdk.js)

View File

@ -46,6 +46,7 @@
<script type="text/javascript" src="shared/js/validate.js"></script>
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
<script type="text/javascript" src="shared/js/websocket.js"></script>
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="js/standaloneAppStore.js"></script>
<script type="text/javascript" src="js/standaloneClient.js"></script>
<script type="text/javascript" src="js/standaloneRoomViews.js"></script>

View File

@ -11,8 +11,42 @@ loop.standaloneRoomViews = (function() {
"use strict";
var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
mixins: [Backbone.Events],
propTypes: {
activeRoomStore:
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired
},
getInitialState: function() {
return this.props.activeRoomStore.getStoreState();
},
componentWillMount: function() {
this.listenTo(this.props.activeRoomStore, "change",
this._onActiveRoomStateChanged);
},
/**
* Handles a "change" event on the roomStore, and updates this.state
* to match the store.
*
* @private
*/
_onActiveRoomStateChanged: function() {
this.setState(this.props.activeRoomStore.getStoreState());
},
componentWillUnmount: function() {
this.stopListening(this.props.activeRoomStore);
},
render: function() {
return (React.DOM.div(null, "Room"));
return (
React.DOM.div(null,
React.DOM.div(null, this.state.roomState)
)
);
}
});

View File

@ -11,8 +11,42 @@ loop.standaloneRoomViews = (function() {
"use strict";
var StandaloneRoomView = React.createClass({
mixins: [Backbone.Events],
propTypes: {
activeRoomStore:
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired
},
getInitialState: function() {
return this.props.activeRoomStore.getStoreState();
},
componentWillMount: function() {
this.listenTo(this.props.activeRoomStore, "change",
this._onActiveRoomStateChanged);
},
/**
* Handles a "change" event on the roomStore, and updates this.state
* to match the store.
*
* @private
*/
_onActiveRoomStateChanged: function() {
this.setState(this.props.activeRoomStore.getStoreState());
},
componentWillUnmount: function() {
this.stopListening(this.props.activeRoomStore);
},
render: function() {
return (<div>Room</div>);
return (
<div>
<div>{this.state.roomState}</div>
</div>
);
}
});

View File

@ -286,7 +286,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_handleRingingProgress: function() {
this.play("ringing", {loop: true});
this.play("ringtone", {loop: true});
this.setState({callState: "ringing"});
},
@ -534,8 +534,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Ended conversation view.
*/
var EndedConversationView = React.createClass({displayName: 'EndedConversationView',
mixins: [sharedMixins.AudioMixin],
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
@ -544,10 +542,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("terminated");
},
render: function() {
document.title = mozL10n.get("standalone_title_with_status",
{clientShortname: mozL10n.get("clientShortname2"),
@ -897,7 +891,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
// XXX New types for flux style
standaloneAppStore: React.PropTypes.instanceOf(
loop.store.StandaloneAppStore).isRequired
loop.store.StandaloneAppStore).isRequired,
activeRoomStore: React.PropTypes.instanceOf(
loop.store.ActiveRoomStore).isRequired
},
getInitialState: function() {
@ -939,7 +935,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
);
}
case "room": {
return loop.standaloneRoomViews.StandaloneRoomView(null);
return (
loop.standaloneRoomViews.StandaloneRoomView({
activeRoomStore: this.props.activeRoomStore}
)
);
}
case "home": {
return HomeView(null);
@ -989,6 +989,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: helper,
sdk: OT
});
var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
// XXX Bug 1074702 will introduce a mozLoop compatible object for
// the standalone rooms.
mozLoop: {}
});
React.renderComponent(WebappRootView({
client: client,
@ -997,7 +1003,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
notifications: notifications,
sdk: OT,
feedbackApiClient: feedbackApiClient,
standaloneAppStore: standaloneAppStore}
standaloneAppStore: standaloneAppStore,
activeRoomStore: activeRoomStore}
), document.querySelector("#main"));
// Set the 'lang' and 'dir' attributes to <html> when the page is translated

View File

@ -286,7 +286,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
_handleRingingProgress: function() {
this.play("ringing", {loop: true});
this.play("ringtone", {loop: true});
this.setState({callState: "ringing"});
},
@ -534,8 +534,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Ended conversation view.
*/
var EndedConversationView = React.createClass({
mixins: [sharedMixins.AudioMixin],
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
@ -544,10 +542,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
componentDidMount: function() {
this.play("terminated");
},
render: function() {
document.title = mozL10n.get("standalone_title_with_status",
{clientShortname: mozL10n.get("clientShortname2"),
@ -897,7 +891,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
// XXX New types for flux style
standaloneAppStore: React.PropTypes.instanceOf(
loop.store.StandaloneAppStore).isRequired
loop.store.StandaloneAppStore).isRequired,
activeRoomStore: React.PropTypes.instanceOf(
loop.store.ActiveRoomStore).isRequired
},
getInitialState: function() {
@ -939,7 +935,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
);
}
case "room": {
return <loop.standaloneRoomViews.StandaloneRoomView/>;
return (
<loop.standaloneRoomViews.StandaloneRoomView
activeRoomStore={this.props.activeRoomStore}
/>
);
}
case "home": {
return <HomeView />;
@ -989,6 +989,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: helper,
sdk: OT
});
var activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
// XXX Bug 1074702 will introduce a mozLoop compatible object for
// the standalone rooms.
mozLoop: {}
});
React.renderComponent(<WebappRootView
client={client}
@ -998,6 +1004,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
sdk={OT}
feedbackApiClient={feedbackApiClient}
standaloneAppStore={standaloneAppStore}
activeRoomStore={activeRoomStore}
/>, document.querySelector("#main"));
// Set the 'lang' and 'dir' attributes to <html> when the page is translated

View File

@ -7,7 +7,7 @@ describe("loop.conversationViews", function () {
"use strict";
var sharedUtils = loop.shared.utils;
var sandbox, oldTitle, view, dispatcher, contact;
var sandbox, oldTitle, view, dispatcher, contact, fakeAudioXHR;
var CALL_STATES = loop.store.CALL_STATES;
@ -30,11 +30,39 @@ describe("loop.conversationViews", function () {
pref: true
}]
};
fakeAudioXHR = {
open: sinon.spy(),
send: function() {},
abort: function() {},
getResponseHeader: function(header) {
if (header === "Content-Type")
return "audio/ogg";
},
responseType: null,
response: new ArrayBuffer(10),
onload: null
};
navigator.mozLoop = {
getLoopCharPref: sinon.stub().returns("http://fakeurl"),
composeEmail: sinon.spy(),
get appVersionInfo() {
return {
version: "42",
channel: "test",
platform: "test"
};
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
})
};
});
afterEach(function() {
document.title = oldTitle;
view = undefined;
delete navigator.mozLoop;
sandbox.restore();
});
@ -202,7 +230,7 @@ describe("loop.conversationViews", function () {
});
describe("CallFailedView", function() {
var store;
var store, fakeAudio;
function mountTestComponent(props) {
return TestUtils.renderIntoDocument(
@ -219,6 +247,12 @@ describe("loop.conversationViews", function () {
client: {},
sdkDriver: {}
});
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
});
it("should dispatch a retryCall action when the retry button is pressed",
@ -306,6 +340,16 @@ describe("loop.conversationViews", function () {
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(false);
});
it("should play a failure sound, once", function() {
view = mountTestComponent();
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"failure", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
describe("OngoingConversationView", function() {
@ -412,11 +456,6 @@ describe("loop.conversationViews", function () {
}
beforeEach(function() {
navigator.mozLoop = {
getLoopCharPref: function() { return "fake"; },
appVersionInfo: sinon.spy()
};
store = new loop.store.ConversationStore({}, {
dispatcher: dispatcher,
client: {},
@ -424,10 +463,6 @@ describe("loop.conversationViews", function () {
});
});
afterEach(function() {
delete navigator.mozLoop;
});
it("should render the CallFailedView when the call state is 'terminated'",
function() {
store.set({callState: CALL_STATES.TERMINATED});

View File

@ -57,7 +57,10 @@ describe("loop.conversation", function() {
channel: "test",
platform: "test"
};
}
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: 'audio/ogg'}));
})
};
// XXX These stubs should be hoisted in a common file
@ -690,8 +693,8 @@ describe("loop.conversation", function() {
function() {
conversation.trigger("session:network-disconnected");
TestUtils.findRenderedComponentWithType(icView,
loop.conversation.GenericFailureView);
TestUtils.findRenderedComponentWithType(icView,
loop.conversation.GenericFailureView);
});
it("should update the conversation window toolbar title",
@ -747,7 +750,7 @@ describe("loop.conversation", function() {
});
describe("IncomingCallView", function() {
var view, model;
var view, model, fakeAudio;
beforeEach(function() {
var Model = Backbone.Model.extend({
@ -757,6 +760,13 @@ describe("loop.conversation", function() {
sandbox.spy(model, "trigger");
sandbox.stub(model, "set");
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
model: model,
video: true
@ -896,4 +906,32 @@ describe("loop.conversation", function() {
});
});
});
describe("GenericFailureView", function() {
var view, fakeAudio;
beforeEach(function() {
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
view = TestUtils.renderIntoDocument(
loop.conversation.GenericFailureView({
cancelCall: function() {}
})
);
});
it("should play a failure sound, once", function() {
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"failure", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
});

View File

@ -3,17 +3,32 @@ var expect = chai.expect;
describe("loop.roomViews", function () {
"use strict";
var sandbox, dispatcher, roomStore, activeRoomStore, fakeWindow, fakeMozLoop,
fakeRoomId;
var ROOM_STATES = loop.store.ROOM_STATES;
var sandbox, dispatcher, roomStore, activeRoomStore, fakeWindow;
beforeEach(function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
fakeWindow = { document: {} };
fakeWindow = {
document: {},
navigator: {
mozLoop: {
getAudioBlob: sinon.stub()
}
}
};
loop.shared.mixins.setRootObject(fakeWindow);
// XXX These stubs should be hoisted in a common file
// Bug 1040968
sandbox.stub(document.mozL10n, "get", function(x) {
return x;
});
activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: {}
@ -26,11 +41,13 @@ describe("loop.roomViews", function () {
});
afterEach(function() {
sinon.sandbox.restore();
sandbox.restore();
loop.shared.mixins.setRootObject(window);
});
describe("DesktopRoomView", function() {
var view;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
new loop.roomViews.DesktopRoomView({
@ -43,10 +60,22 @@ describe("loop.roomViews", function () {
it("should set document.title to store.serverData.roomName", function() {
mountTestComponent();
activeRoomStore.setStoreState({serverData: {roomName: "fakeName"}});
activeRoomStore.setStoreState({roomName: "fakeName"});
expect(fakeWindow.document.title).to.equal("fakeName");
});
it("should render the GenericFailureView if the roomState is `FAILED`", function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.conversation.GenericFailureView);
});
// XXX Implement this when we do the rooms views in bug 1074686 and others.
it("should display the main view");
});
});
});

View File

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

View File

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

View File

@ -6,6 +6,8 @@
* 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() {

View File

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

View File

@ -4,6 +4,7 @@
/*
* This file contains tests for the mozLoop telemetry API.
*/
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);

View File

@ -7,6 +7,8 @@
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
registerCleanupFunction(function*() {
MozLoopService.doNotDisturb = false;
MozLoopServiceInternal.fxAOAuthProfile = null;

View File

@ -6,11 +6,27 @@ var sharedActions = loop.shared.actions;
describe("loop.store.ActiveRoomStore", function () {
"use strict";
var sandbox, dispatcher;
var ROOM_STATES = loop.store.ROOM_STATES;
var sandbox, dispatcher, store, fakeMozLoop;
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.useFakeTimers();
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");
fakeMozLoop = {
rooms: {
get: sandbox.stub(),
join: sandbox.stub(),
refreshMembership: sandbox.stub(),
leave: sandbox.stub()
}
};
store = new loop.store.ActiveRoomStore(
{mozLoop: fakeMozLoop, dispatcher: dispatcher});
});
afterEach(function() {
@ -31,73 +47,96 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#roomFailure", function() {
var fakeError;
beforeEach(function() {
sandbox.stub(console, "error");
fakeError = new Error("fake");
store.setStoreState({
roomState: ROOM_STATES.READY
});
});
it("should log the error", function() {
store.roomFailure({error: fakeError});
sinon.assert.calledOnce(console.error);
sinon.assert.calledWith(console.error,
sinon.match(ROOM_STATES.READY), fakeError);
});
it("should set the state to `FAILED`", function() {
store.roomFailure({error: fakeError});
expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
});
});
describe("#setupWindowData", function() {
var store, fakeMozLoop, fakeToken, fakeRoomName;
var fakeToken, fakeRoomData;
beforeEach(function() {
fakeToken = "337-ff-54";
fakeRoomName = "Monkeys";
fakeMozLoop = {
rooms: { get: sandbox.stub() }
fakeRoomData = {
roomName: "Monkeys",
roomOwner: "Alfred",
roomUrl: "http://invalid"
};
store = new loop.store.ActiveRoomStore(
{mozLoop: fakeMozLoop, dispatcher: dispatcher});
fakeMozLoop.rooms.get.
withArgs(fakeToken).
callsArgOnWith(1, // index of callback argument
store, // |this| to call it on
null, // args to call the callback with...
{roomName: fakeRoomName}
fakeRoomData
);
});
it("should trigger a change event", function(done) {
store.on("change", function() {
done();
});
dispatcher.dispatch(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
it("should set roomToken on the store from the action data",
function(done) {
store.once("change", function () {
expect(store.getStoreState()).
to.have.property('roomToken', fakeToken);
done();
});
dispatcher.dispatch(new sharedActions.SetupWindowData({
it("should set the state to `GATHER`",
function() {
store.setupWindowData(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
expect(store.getStoreState()).
to.have.property('roomState', ROOM_STATES.GATHER);
});
it("should set serverData.roomName from the getRoomData callback",
function(done) {
store.once("change", function () {
expect(store.getStoreState()).to.have.deep.property(
'serverData.roomName', fakeRoomName);
done();
});
dispatcher.dispatch(new sharedActions.SetupWindowData({
it("should dispatch an UpdateRoomInfo action if the get is successful",
function() {
store.setupWindowData(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomToken: fakeToken
}, fakeRoomData)));
});
it("should set error on the store when getRoomData calls back an error",
function(done) {
it("should dispatch a JoinRoom action if the get is successful",
function() {
store.setupWindowData(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.JoinRoom());
});
it("should dispatch a RoomFailure action if the get fails",
function() {
var fakeError = new Error("fake error");
fakeMozLoop.rooms.get.
@ -106,17 +145,201 @@ describe("loop.store.ActiveRoomStore", function () {
store, // |this| to call it on
fakeError); // args to call the callback with...
store.once("change", function() {
expect(this.getStoreState()).to.have.property('error', fakeError);
done();
});
dispatcher.dispatch(new sharedActions.SetupWindowData({
store.setupWindowData(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RoomFailure({
error: fakeError
}));
});
});
describe("#updateRoomInfo", function() {
var fakeRoomInfo;
beforeEach(function() {
fakeRoomInfo = {
roomName: "Its a room",
roomOwner: "Me",
roomToken: "fakeToken",
roomUrl: "http://invalid"
};
});
it("should set the state to READY", function() {
store.updateRoomInfo(fakeRoomInfo);
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
});
it("should save the room information", function() {
store.updateRoomInfo(fakeRoomInfo);
var state = store.getStoreState();
expect(state.roomName).eql(fakeRoomInfo.roomName);
expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
expect(state.roomToken).eql(fakeRoomInfo.roomToken);
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
});
});
describe("#joinRoom", function() {
beforeEach(function() {
store.setStoreState({roomToken: "tokenFake"});
});
it("should call rooms.join on mozLoop", function() {
store.joinRoom();
sinon.assert.calledOnce(fakeMozLoop.rooms.join);
sinon.assert.calledWith(fakeMozLoop.rooms.join, "tokenFake");
});
it("should dispatch `JoinedRoom` on success", function() {
var responseData = {
apiKey: "keyFake",
sessionToken: "14327659860",
sessionId: "1357924680",
expires: 8
};
fakeMozLoop.rooms.join.callsArgWith(1, null, responseData);
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.JoinedRoom(responseData));
});
it("should dispatch `RoomFailure` on error", function() {
var fakeError = new Error("fake");
fakeMozLoop.rooms.join.callsArgWith(1, fakeError);
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.RoomFailure({error: fakeError}));
});
});
describe("#joinedRoom", function() {
var fakeJoinedData;
beforeEach(function() {
fakeJoinedData = {
apiKey: "9876543210",
sessionToken: "12563478",
sessionId: "15263748",
expires: 20
};
store.setStoreState({
roomToken: "fakeToken"
});
});
it("should set the state to `JOINED`", function() {
store.joinedRoom(fakeJoinedData);
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
});
it("should store the session and api values", function() {
store.joinedRoom(fakeJoinedData);
var state = store.getStoreState();
expect(state.apiKey).eql(fakeJoinedData.apiKey);
expect(state.sessionToken).eql(fakeJoinedData.sessionToken);
expect(state.sessionId).eql(fakeJoinedData.sessionId);
});
it("should call mozLoop.rooms.refreshMembership before the expiresTime",
function() {
store.joinedRoom(fakeJoinedData);
sandbox.clock.tick(fakeJoinedData.expires * 1000);
sinon.assert.calledOnce(fakeMozLoop.rooms.refreshMembership);
sinon.assert.calledWith(fakeMozLoop.rooms.refreshMembership,
"fakeToken", "12563478");
});
it("should call mozLoop.rooms.refreshMembership before the next expiresTime",
function() {
fakeMozLoop.rooms.refreshMembership.callsArgWith(2,
null, {expires: 40});
store.joinedRoom(fakeJoinedData);
// Clock tick for the first expiry time (which
// sets up the refreshMembership).
sandbox.clock.tick(fakeJoinedData.expires * 1000);
// Clock tick for expiry time in the refresh membership response.
sandbox.clock.tick(40000);
sinon.assert.calledTwice(fakeMozLoop.rooms.refreshMembership);
sinon.assert.calledWith(fakeMozLoop.rooms.refreshMembership,
"fakeToken", "12563478");
});
it("should dispatch `RoomFailure` if the refreshMembership call failed",
function() {
var fakeError = new Error("fake");
fakeMozLoop.rooms.refreshMembership.callsArgWith(2, fakeError);
store.joinedRoom(fakeJoinedData);
// Clock tick for the first expiry time (which
// sets up the refreshMembership).
sandbox.clock.tick(fakeJoinedData.expires * 1000);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.RoomFailure({
error: fakeError
}));
});
});
describe("#windowUnload", function() {
beforeEach(function() {
store.setStoreState({
roomState: ROOM_STATES.JOINED,
roomToken: "fakeToken",
sessionToken: "1627384950"
});
});
it("should clear any existing timeout", function() {
sandbox.stub(window, "clearTimeout");
store._timeout = {};
store.windowUnload();
sinon.assert.calledOnce(clearTimeout);
});
it("should call mozLoop.rooms.leave", function() {
store.windowUnload();
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
"fakeToken", "1627384950");
});
it("should set the state to ready", function() {
store.windowUnload();
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
});
});
});

View File

@ -15,7 +15,7 @@ describe("loop.shared.views", function() {
var sharedModels = loop.shared.models,
sharedViews = loop.shared.views,
getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass,
sandbox;
sandbox, fakeAudioXHR;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -23,6 +23,18 @@ describe("loop.shared.views", function() {
sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
});
fakeAudioXHR = {
open: sinon.spy(),
send: function() {},
abort: function() {},
getResponseHeader: function(header) {
if (header === "Content-Type")
return "audio/ogg";
},
responseType: null,
response: new ArrayBuffer(10),
onload: null
};
});
afterEach(function() {
@ -368,16 +380,55 @@ describe("loop.shared.views", function() {
it("should play a connected sound, once, on session:connected",
function() {
var url = "shared/sounds/connected.ogg";
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
model.trigger("session:connected");
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(
window.Audio, "shared/sounds/connected.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(fakeAudioXHR.open, "GET", url, true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});
});
describe("for desktop", function() {
var origMozLoop;
beforeEach(function() {
origMozLoop = navigator.mozLoop;
navigator.mozLoop = {
getAudioBlob: sinon.spy(function(name, callback) {
var data = new ArrayBuffer(10);
callback(null, new Blob([data], {type: "audio/ogg"}));
})
};
});
afterEach(function() {
navigator.mozLoop = origMozLoop;
});
it("should play a connected sound, once, on session:connected",
function() {
var url = "chrome://browser/content/loop/shared/sounds/connected.ogg";
model.trigger("session:connected");
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"connected", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});
});
describe("for both (standalone and desktop)", function() {
beforeEach(function() {
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
it("should start streaming on session:connected", function() {
model.trigger("session:connected");
@ -458,6 +509,7 @@ describe("loop.shared.views", function() {
beforeEach(function() {
fakeFeedbackApiClient = {send: sandbox.stub()};
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
feedbackApiClient: fakeFeedbackApiClient
}));

View File

@ -40,6 +40,7 @@
<script src="../../content/shared/js/actions.js"></script>
<script src="../../content/shared/js/validate.js"></script>
<script src="../../content/shared/js/dispatcher.js"></script>
<script src="../../content/shared/js/activeRoomStore.js"></script>
<script src="../../standalone/content/js/multiplexGum.js"></script>
<script src="../../standalone/content/js/standaloneAppStore.js"></script>
<script src="../../standalone/content/js/standaloneClient.js"></script>

View File

@ -18,7 +18,8 @@ describe("loop.webapp", function() {
sandbox,
notifications,
feedbackApiClient,
stubGetPermsAndCacheMedia;
stubGetPermsAndCacheMedia,
fakeAudioXHR;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -29,6 +30,19 @@ describe("loop.webapp", function() {
stubGetPermsAndCacheMedia = sandbox.stub(
loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
fakeAudioXHR = {
open: sinon.spy(),
send: function() {},
abort: function() {},
getResponseHeader: function(header) {
if (header === "Content-Type")
return "audio/ogg";
},
responseType: null,
response: new ArrayBuffer(10),
onload: null
};
});
afterEach(function() {
@ -219,6 +233,7 @@ describe("loop.webapp", function() {
describe("state: terminate, reason: reject", function() {
beforeEach(function() {
sandbox.stub(notifications, "errorL10n");
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
it("should display the FailedConversationView", function() {
@ -307,6 +322,7 @@ describe("loop.webapp", function() {
promiseConnectStub =
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect");
promiseConnectStub.returns(new Promise(function(resolve, reject) {}));
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
});
describe("call:outgoing", function() {
@ -526,6 +542,8 @@ describe("loop.webapp", function() {
var view, conversation, client, fakeAudio;
beforeEach(function() {
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
@ -541,6 +559,7 @@ describe("loop.webapp", function() {
});
conversation.set("loopToken", "fakeToken");
sandbox.stub(client, "requestCallUrlInfo");
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.FailedConversationView({
conversation: conversation,
@ -550,9 +569,12 @@ describe("loop.webapp", function() {
});
it("should play a failure sound, once", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio,
"shared/sounds/failure.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/failure.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
@ -560,7 +582,7 @@ describe("loop.webapp", function() {
describe("WebappRootView", function() {
var helper, sdk, conversationModel, client, props, standaloneAppStore;
var dispatcher;
var dispatcher, activeRoomStore;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@ -571,7 +593,8 @@ describe("loop.webapp", function() {
sdk: sdk,
conversation: conversationModel,
feedbackApiClient: feedbackApiClient,
standaloneAppStore: standaloneAppStore
standaloneAppStore: standaloneAppStore,
activeRoomStore: activeRoomStore
}));
}
@ -587,6 +610,10 @@ describe("loop.webapp", function() {
baseServerUrl: "fakeUrl"
});
dispatcher = new loop.Dispatcher();
activeRoomStore = new loop.store.ActiveRoomStore({
dispatcher: dispatcher,
mozLoop: {}
});
standaloneAppStore = new loop.store.StandaloneAppStore({
dispatcher: dispatcher,
sdk: sdk,
@ -678,6 +705,7 @@ describe("loop.webapp", function() {
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.PendingConversationView({
@ -689,8 +717,12 @@ describe("loop.webapp", function() {
describe("#componentDidMount", function() {
it("should play a looped connecting sound", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/connecting.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/connecting.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(true);
});
@ -727,8 +759,13 @@ describe("loop.webapp", function() {
it("should play a looped ringing sound", function() {
websocket.trigger("progress:alerting");
fakeAudioXHR.onload();
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/ringing.ogg");
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/ringtone.ogg", true);
sinon.assert.called(fakeAudio.play);
expect(fakeAudio.loop).to.equal(true);
});
});
@ -997,6 +1034,7 @@ describe("loop.webapp", function() {
conversation = new sharedModels.ConversationModel({}, {
sdk: {}
});
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.EndedConversationView({
conversation: conversation,
@ -1018,8 +1056,13 @@ describe("loop.webapp", function() {
describe("#componentDidMount", function() {
it("should play a terminating sound, once", function() {
sinon.assert.calledOnce(window.Audio);
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/terminated.ogg");
fakeAudioXHR.onload();
sinon.assert.called(fakeAudioXHR.open);
sinon.assert.calledWithExactly(
fakeAudioXHR.open, "GET", "shared/sounds/terminated.ogg", true);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.not.equal(true);
});

View File

@ -212,7 +212,16 @@ add_task(function* setup_server() {
// Add a request handler for each room in the list.
[...kRooms.values()].forEach(function(room) {
loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
returnRoomDetails(res, room.roomName);
if (req.method == "POST") {
let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
let data = JSON.parse(body);
res.setStatusLine(null, 200, "OK");
res.write(JSON.stringify(data));
res.processAsync();
res.finish();
} else {
returnRoomDetails(res, room.roomName);
}
});
});
@ -321,6 +330,39 @@ add_task(function* test_roomUpdates() {
yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
});
// Test if joining a room works as expected.
add_task(function* test_joinRoom() {
// We need these set up for getting the email address.
Services.prefs.setCharPref("loop.fxa_oauth.profile", JSON.stringify({
email: "fake@invalid.com"
}));
Services.prefs.setCharPref("loop.fxa_oauth.tokendata", JSON.stringify({
token_type: "bearer"
}));
let roomToken = "_nxD4V4FflQ";
let joinedData = yield LoopRooms.promise("join", roomToken);
Assert.equal(joinedData.action, "join");
Assert.equal(joinedData.displayName, "fake@invalid.com");
});
// Test if refreshing a room works as expected.
add_task(function* test_refreshMembership() {
let roomToken = "_nxD4V4FflQ";
let refreshedData = yield LoopRooms.promise("refreshMembership", roomToken,
"fakeSessionToken");
Assert.equal(refreshedData.action, "refresh");
Assert.equal(refreshedData.sessionToken, "fakeSessionToken");
});
// Test if leaving a room works as expected.
add_task(function* test_leaveRoom() {
let roomToken = "_nxD4V4FflQ";
let leaveData = yield LoopRooms.promise("leave", roomToken, "fakeLeaveSessionToken");
Assert.equal(leaveData.action, "leave");
Assert.equal(leaveData.sessionToken, "fakeLeaveSessionToken");
});
// Test if the event emitter implementation doesn't leak and is working as expected.
add_task(function* () {
Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");
@ -339,6 +381,9 @@ function run_test() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
Services.prefs.clearUserPref("loop.fxa_oauth.profile");
Services.prefs.clearUserPref("loop.fxa_oauth.tokendata");
LoopRooms.off("add", onRoomAdded);
LoopRooms.off("update", onRoomUpdated);
LoopRooms.off("joined", onRoomJoined);

View File

@ -5,6 +5,7 @@
// This test makes sure that the URL bar is focused when entering the private window.
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function checkUrlbarFocus(win) {
let urlbar = win.gURLBar;

View File

@ -198,7 +198,7 @@ ContentRestoreInternal.prototype = {
}
let referrer = loadArguments.referrer ?
Utils.makeURI(loadArguments.referrer) : null;
webNavigation.loadURI(loadArguments.uri, loadArguments.loadFlags,
webNavigation.loadURI(loadArguments.uri, loadArguments.flags,
referrer, null, null);
} else if (tabData.userTypedValue && tabData.userTypedClear) {
// If the user typed a URL into the URL bar and hit enter right before

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
waitForExplicitFinish();
newWindowWithTabView(onTabViewWindowLoaded);

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
let cw;
let win;

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
let cw;
let win;

View File

@ -15,6 +15,8 @@ const TEST_URL = 'data:text/html,<script>window.onbeforeunload=' +
let contentWindow;
let activeGroup;
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
waitForExplicitFinish();

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
waitForExplicitFinish();

View File

@ -3,6 +3,8 @@
let cw;
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
requestLongerTimeout(2);
waitForExplicitFinish();

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/Promise.jsm", this);
function test() {
waitForExplicitFinish();

View File

@ -2,6 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
const STATE = {
windows: [{

View File

@ -14,28 +14,29 @@ function test() {
info("highlighting the body node");
yield runCommand("highlight body", options);
is(getHighlighters().length, 1, "The highlighter element exists for body");
is(getHighlighterNumber(), 1, "The highlighter element exists for body");
info("highlighting the div node");
yield runCommand("highlight div", options);
is(getHighlighters().length, 1, "The highlighter element exists for div");
is(getHighlighterNumber(), 1, "The highlighter element exists for div");
info("highlighting the body node again, asking to keep the div");
yield runCommand("highlight body --keep", options);
is(getHighlighters().length, 2, "2 highlighter elements have been created");
is(getHighlighterNumber(), 2, "2 highlighter elements have been created");
info("unhighlighting all nodes");
yield runCommand("unhighlight", options);
is(getHighlighters().length, 0, "All highlighters have been removed");
is(getHighlighterNumber(), 0, "All highlighters have been removed");
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
}).then(finish, helpers.handleError);
}
function getHighlighters() {
return gBrowser.selectedBrowser.parentNode
.querySelectorAll(".highlighter-container");
function getHighlighterNumber() {
// Note that this only works as long as gcli tests aren't run with e10s on.
// To make this e10s ready, execute this in a content frame script instead.
return require("gcli/commands/highlight").highlighters.length;
}
function* runCommand(cmd, options) {

View File

@ -215,6 +215,8 @@ let DebuggerController = {
yield this._startTracingTab(traceActor);
}
}
this._hideUnsupportedFeatures();
}),
/**
@ -231,6 +233,16 @@ let DebuggerController = {
this.activeThread = null;
},
_hideUnsupportedFeatures: function() {
if (this.client.mainRoot.traits.noPrettyPrinting) {
DebuggerView.Sources.hidePrettyPrinting();
}
if (this.client.mainRoot.traits.noBlackBoxing) {
DebuggerView.Sources.hideBlackBoxing();
}
},
/**
* Called for each location change in the debugged tab.
*

View File

@ -546,6 +546,24 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
}
},
hidePrettyPrinting: function() {
this._prettyPrintButton.style.display = 'none';
if (this._blackBoxButton.style.display === 'none') {
let sep = document.querySelector('#sources-toolbar .devtools-separator');
sep.style.display = 'none';
}
},
hideBlackBoxing: function() {
this._blackBoxButton.style.display = 'none';
if (this._prettyPrintButton.style.display === 'none') {
let sep = document.querySelector('#sources-toolbar .devtools-separator');
sep.style.display = 'none';
}
},
/**
* Marks a breakpoint as selected in this sources container.
*

View File

@ -235,6 +235,8 @@ skip-if = e10s
skip-if = e10s
[browser_dbg_globalactor.js]
skip-if = e10s
[browser_dbg_hide-toolbar-buttons.js]
skip-if = e10s
[browser_dbg_hit-counts-01.js]
skip-if = e10s
[browser_dbg_hit-counts-02.js]

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 1093349: Test that the pretty-printing and blackboxing buttons
* are hidden if the server doesn't support them
*/
const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-01.html";
let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let { RootActor } = devtools.require("devtools/server/actors/root");
function test() {
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
RootActor.prototype.traits.noBlackBoxing = true;
RootActor.prototype.traits.noPrettyPrinting = true;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
let document = aPanel.panelWin.document;
let ppButton = document.querySelector('#pretty-print');
let bbButton = document.querySelector('#black-box');
let sep = document.querySelector('#sources-toolbar .devtools-separator');
is(ppButton.style.display, 'none', 'The pretty-print button is hidden');
is(bbButton.style.display, 'none', 'The blackboxing button is hidden');
is(sep.style.display, 'none', 'The separator is hidden');
closeDebuggerAndFinish(aPanel)
});
}

View File

@ -2,6 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
window {
-moz-appearance: none;
background-color: transparent;
}
#doorhanger-container {
width: 450px;
}
@ -10,7 +15,20 @@
padding: 20px;
background: #343c45; /* toolbars */
color: #8fa1b2; /* body text */
/*
* Sloppy preprocessing since UNIX_BUT_NOT_MAC is only defined
* in `browser/app/profile/firefox.js`, which this file cannot
* depend on. Must style font-size to target linux.
*/
%ifdef XP_UNIX
%ifndef XP_MACOSX
font-size: 13px;
%else
font-size: 15px;
%endif
%else
font-size: 15px;
%endif
line-height: 19px;
min-height: 100px;
}
@ -60,6 +78,8 @@
border-radius: 5px;
height: 30px;
width: 450px;
/* Override embossed borders on Windows/Linux */
border: none;
}
#close {

View File

@ -1,22 +1,24 @@
[DEFAULT]
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
skip-if = e10s # Bug 1074836 - inspector tests disabled with e10s
subsuite = devtools
support-files =
doc_frame_script.js
doc_inspector_breadcrumbs.html
doc_inspector_delete-selected-node-01.html
doc_inspector_delete-selected-node-02.html
doc_inspector_gcli-inspect-command.html
doc_inspector_highlight_after_transition.html
doc_inspector_highlighter-comments.html
doc_inspector_highlighter_csstransform.html
doc_inspector_highlighter.html
browser_inspector_infobar_01.html
browser_inspector_infobar_02.html
doc_inspector_infobar_01.html
doc_inspector_infobar_02.html
doc_inspector_menu.html
doc_inspector_remove-iframe-during-load.html
doc_inspector_search.html
doc_inspector_search-suggestions.html
doc_inspector_select-last-selected-01.html
doc_inspector_select-last-selected-02.html
browser_inspector_highlight_after_transition.html
head.js
[browser_inspector_breadcrumbs.js]
@ -29,11 +31,20 @@ support-files =
[browser_inspector_highlighter-01.js]
[browser_inspector_highlighter-02.js]
[browser_inspector_highlighter-03.js]
[browser_inspector_highlighter-04.js]
[browser_inspector_highlighter-by-type.js]
[browser_inspector_highlighter-comments.js]
[browser_inspector_highlighter-csstransform_01.js]
[browser_inspector_highlighter-csstransform_02.js]
[browser_inspector_highlighter-hover_01.js]
[browser_inspector_highlighter-hover_02.js]
[browser_inspector_highlighter-hover_03.js]
[browser_inspector_highlighter-iframes.js]
[browser_inspector_highlighter-options.js]
[browser_inspector_highlighter-selector_01.js]
[browser_inspector_highlighter-selector_02.js]
[browser_inspector_iframe-navigation.js]
[browser_inspector_infobar_01.js]
[browser_inspector_infobar_02.js]
[browser_inspector_initialization.js]
[browser_inspector_inspect-object-element.js]
[browser_inspector_invalidate.js]
@ -57,4 +68,3 @@ support-files =
[browser_inspector_sidebarstate.js]
[browser_inspector_switch-to-inspector-on-pick.js]
[browser_inspector_update-on-navigation.js]

View File

@ -7,70 +7,67 @@
const TEST_URI = TEST_URL_ROOT + "doc_inspector_breadcrumbs.html";
const NODES = [
{nodeId: "#i1111", result: "i1 i11 i111 i1111"},
{nodeId: "#i22", result: "i2 i22 i221"},
{nodeId: "#i2111", result: "i2 i21 i211 i2111"},
{nodeId: "#i21", result: "i2 i21 i211 i2111"},
{nodeId: "#i22211", result: "i2 i22 i222 i2221 i22211"},
{nodeId: "#i22", result: "i2 i22 i222 i2221 i22211"},
{selector: "#i1111", result: "i1 i11 i111 i1111"},
{selector: "#i22", result: "i2 i22 i221"},
{selector: "#i2111", result: "i2 i21 i211 i2111"},
{selector: "#i21", result: "i2 i21 i211 i2111"},
{selector: "#i22211", result: "i2 i22 i222 i2221 i22211"},
{selector: "#i22", result: "i2 i22 i222 i2221 i22211"},
];
let test = asyncTest(function*() {
add_task(function*() {
let { inspector } = yield openInspectorForURL(TEST_URI);
let container = inspector.panelDoc.getElementById("inspector-breadcrumbs");
for (let node of NODES) {
info("Testing node " + node.nodeId);
let documentNode = getNode(node.nodeId);
info("Testing node " + node.selector);
info("Selecting node and waiting for breadcrumbs to update");
let breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
let nodeSelected = selectNode(documentNode, inspector);
yield selectNode(node.selector, inspector);
yield breadcrumbsUpdated;
yield Promise.all([breadcrumbsUpdated, nodeSelected]);
info("Performing checks for node " + node.nodeId);
info("Performing checks for node " + node.selector);
let buttonsLabelIds = node.result.split(" ");
// html > body > …
is(container.childNodes.length, buttonsLabelIds.length + 2,
"Node " + node.nodeId + ": Items count");
"Node " + node.selector + ": Items count");
for (let i = 2; i < container.childNodes.length; i++) {
let expectedId = "#" + buttonsLabelIds[i - 2];
let button = container.childNodes[i];
let labelId = button.querySelector(".breadcrumbs-widget-item-id");
is(labelId.textContent, expectedId,
"Node #" + node.nodeId + ": button " + i + " matches");
"Node #" + node.selector + ": button " + i + " matches");
}
let checkedButton = container.querySelector("button[checked]");
let labelId = checkedButton.querySelector(".breadcrumbs-widget-item-id");
let id = inspector.selection.node.id;
let id = inspector.selection.nodeFront.id;
is(labelId.textContent, "#" + id,
"Node #" + node.nodeId + ": selection matches");
"Node #" + node.selector + ": selection matches");
}
yield testPseudoElements(inspector, container);
});
function *testPseudoElements(inspector, container) {
function* testPseudoElements(inspector, container) {
info ("Checking for pseudo elements");
let pseudoParent = getNodeFront(getNode("#pseudo-container"));
let pseudoParent = yield getNodeFront("#pseudo-container", inspector);
let children = yield inspector.walker.children(pseudoParent);
is (children.nodes.length, 2, "Pseudo children returned from walker");
let beforeElement = children.nodes[0];
let breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
let nodeSelected = selectNode(beforeElement, inspector);
yield Promise.all([breadcrumbsUpdated, nodeSelected]);
yield selectNode(beforeElement, inspector);
yield breadcrumbsUpdated;
is(container.childNodes[3].textContent, "::before", "::before shows up in breadcrumb");
let afterElement = children.nodes[1];
breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
nodeSelected = selectNode(afterElement, inspector);
yield Promise.all([breadcrumbsUpdated, nodeSelected]);
yield selectNode(afterElement, inspector);
yield breadcrumbsUpdated;
is(container.childNodes[3].textContent, "::after", "::before shows up in breadcrumb");
}

View File

@ -6,7 +6,7 @@
// Test that hovering over nodes on the breadcrumb buttons in the inspector shows the highlighter over
// those nodes
let test = asyncTest(function*() {
add_task(function*() {
info("Loading the test document and opening the inspector");
yield addTab("data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>");
let {toolbox, inspector} = yield openInspector();
@ -18,15 +18,23 @@ let test = asyncTest(function*() {
let button = bcButtons.childNodes[1];
EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"}, button.ownerDocument.defaultView);
yield onNodeHighlighted;
ok(isHighlighting(), "The highlighter is shown on a markup container hover");
is(getHighlitNode(), getNode("body"), "The highlighter highlights the right node");
let isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown on a markup container hover");
let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, getNode("body"), "The highlighter highlights the right node");
onNodeHighlighted = toolbox.once("node-highlight");
button = bcButtons.childNodes[2];
EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"}, button.ownerDocument.defaultView);
yield onNodeHighlighted;
ok(isHighlighting(), "The highlighter is shown on a markup container hover");
is(getHighlitNode(), getNode("span"), "The highlighter highlights the right node");
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown on a markup container hover");
highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, getNode("span"), "The highlighter highlights the right node");
gBrowser.removeCurrentTab();
});

View File

@ -7,22 +7,18 @@
const TEST_URL = TEST_URL_ROOT + "doc_inspector_delete-selected-node-01.html";
let test = asyncTest(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
let iframe = getNode("iframe");
let span = getNode("span", { document: iframe.contentDocument });
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
let span = yield getNodeFrontInFrame("span", "iframe", inspector);
yield selectNode(span, inspector);
info("Removing selected <span> element.");
let parentNode = span.parentNode;
span.remove();
let lh = new LayoutHelpers(window.content);
ok(!lh.isNodeConnected(span), "Node considered as disconnected.");
let parentNode = span.parentNode();
yield inspector.walker.removeNode(span);
// Wait for the inspector to process the mutation
yield inspector.once("inspector-updated");
is(inspector.selection.node, parentNode,
is(inspector.selection.nodeFront, parentNode,
"Parent node of selected <span> got selected.");
});

View File

@ -12,7 +12,7 @@
const TEST_PAGE = TEST_URL_ROOT +
"doc_inspector_delete-selected-node-02.html";
let test = asyncTest(function* () {
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_PAGE);
yield testManuallyDeleteSelectedNode();
@ -23,11 +23,10 @@ let test = asyncTest(function* () {
info("Selecting a node, deleting it via context menu and checking that " +
"its parent node is selected and breadcrumbs are updated.");
let div = getNode("#deleteManually");
yield selectNode(div, inspector);
yield selectNode("#deleteManually", inspector);
info("Getting the node container in the markup view.");
let container = getContainerForRawNode(inspector.markup, div);
let container = yield getContainerForSelector("#deleteManually", inspector);
info("Simulating right-click on the markup view container.");
EventUtils.synthesizeMouse(container.tagLine, 2, 2,
@ -43,26 +42,24 @@ let test = asyncTest(function* () {
yield inspector.once("inspector-updated");
info("Inspector updated, performing checks.");
let parent = getNode("#deleteChildren");
assertNodeSelectedAndPanelsUpdated(parent, "ul#deleteChildren");
yield assertNodeSelectedAndPanelsUpdated("#deleteChildren", "ul#deleteChildren");
}
function* testAutomaticallyDeleteSelectedNode() {
info("Selecting a node, deleting it via javascript and checking that " +
"its parent node is selected and breadcrumbs are updated.");
let div = getNode("#deleteAutomatically");
let div = yield getNodeFront("#deleteAutomatically", inspector);
yield selectNode(div, inspector);
info("Deleting selected node via javascript.");
div.remove();
yield inspector.walker.removeNode(div);
info("Waiting for inspector to update.");
yield inspector.once("inspector-updated");
info("Inspector updated, performing checks.");
let parent = getNode("#deleteChildren");
assertNodeSelectedAndPanelsUpdated(parent, "ul#deleteChildren");
yield assertNodeSelectedAndPanelsUpdated("#deleteChildren", "ul#deleteChildren");
}
function* testDeleteSelectedNodeContainerFrame() {
@ -71,23 +68,23 @@ let test = asyncTest(function* () {
"breadcrumbs are updated.");
info("Selecting an element inside iframe.");
let iframe = getNode("#deleteIframe");
let div = iframe.contentDocument.getElementById("deleteInIframe");
let iframe = yield getNodeFront("#deleteIframe", inspector);
let div = yield getNodeFrontInFrame("#deleteInIframe", iframe, inspector);
yield selectNode(div, inspector);
info("Deleting selected node via javascript.");
iframe.remove();
yield inspector.walker.removeNode(iframe);
info("Waiting for inspector to update.");
yield inspector.once("inspector-updated");
info("Inspector updated, performing checks.");
assertNodeSelectedAndPanelsUpdated(getNode("body"), "body");
yield assertNodeSelectedAndPanelsUpdated("body", "body");
}
function assertNodeSelectedAndPanelsUpdated(node, crumbLabel) {
is(inspector.selection.nodeFront, getNodeFront(node),
"The right node is selected");
function* assertNodeSelectedAndPanelsUpdated(selector, crumbLabel) {
let nodeFront = yield getNodeFront(selector, inspector);
is(inspector.selection.nodeFront, nodeFront, "The right node is selected");
let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs");
is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel,

View File

@ -8,19 +8,18 @@
const TEST_URL = TEST_URL_ROOT + "doc_inspector_delete-selected-node-01.html";
let test = asyncTest(function* () {
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
let iframe = getNode("iframe");
let node = getNode("span", { document: iframe.contentDocument });
let iframe = yield getNodeFront("iframe", inspector);
let node = yield getNodeFrontInFrame("span", iframe, inspector);
yield selectNode(node, inspector);
info("Removing iframe.");
iframe.remove();
yield inspector.walker.removeNode(iframe);
let lh = new LayoutHelpers(window.content);
ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
ok(!inspector.selection.isConnected(), "Selection considered as disconnected.");
let body = yield getNodeFront("body", inspector);
is(inspector.selection.nodeFront, body, "Selection is now the body node");
yield inspector.once("inspector-updated");
});

View File

@ -8,7 +8,7 @@
const URL_1 = "data:text/plain;charset=UTF-8,abcde";
const URL_2 = "data:text/plain;charset=UTF-8,12345";
let test = asyncTest(function* () {
add_task(function* () {
let { toolbox } = yield openInspectorForURL(URL_1);
info("Navigating to different URL.");

View File

@ -7,7 +7,7 @@
const TEST_URI = TEST_URL_ROOT + "doc_inspector_gcli-inspect-command.html";
let test = asyncTest(function* () {
add_task(function* () {
return helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.audit(options, [
{

View File

@ -6,23 +6,28 @@
// Test that hovering over nodes in the markup-view shows the highlighter over
// those nodes
let test = asyncTest(function*() {
add_task(function*() {
info("Loading the test document and opening the inspector");
yield addTab("data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>");
let {toolbox, inspector} = yield openInspector();
let isVisible = yield isHighlighting(toolbox);
ok(!isVisible, "The highlighter is hidden by default");
info("Selecting the test node");
yield selectNode("span", inspector);
let container = getContainerForRawNode(inspector.markup, getNode("h1"));
let container = yield getContainerForSelector("h1", inspector);
let onHighlighterReady = toolbox.once("highlighter-ready");
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
inspector.markup.doc.defaultView);
yield onHighlighterReady;
ok(isHighlighting(), "The highlighter is shown on a markup container hover");
is(getHighlitNode(), getNode("h1"), "The highlighter highlights the right node");
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown on a markup container hover");
let node = yield getHighlitNode(toolbox);
is(node, getNode("h1"), "The highlighter highlights the right node");
gBrowser.removeCurrentTab();
});

View File

@ -10,70 +10,33 @@
const TEST_URI = TEST_URL_ROOT + "doc_inspector_highlighter.html";
let test = asyncTest(function*() {
let { inspector } = yield openInspectorForURL(TEST_URI);
add_task(function*() {
let {toolbox, inspector} = yield openInspectorForURL(TEST_URI);
info("Selecting the simple, non-transformed DIV");
let div = getNode("#simple-div");
yield selectAndHighlightNode(div, inspector);
yield selectAndHighlightNode("#simple-div", inspector);
testSimpleDivHighlighted(div);
yield zoomTo(2);
testZoomedSimpleDivHighlighted(div);
yield zoomTo(1);
let isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown");
let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, getNode("#simple-div"),
"The highlighter's outline corresponds to the simple div");
yield isNodeCorrectlyHighlighted(getNode("#simple-div"), toolbox,
"non-zoomed");
info("Selecting the rotated DIV");
let rotated = getNode("#rotated-div");
let onBoxModelUpdate = waitForBoxModelUpdate();
yield selectAndHighlightNode(rotated, inspector);
yield onBoxModelUpdate;
yield selectAndHighlightNode("#rotated-div", inspector);
testMouseOverRotatedHighlights(rotated);
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#rotated-div"), toolbox,
"rotated");
info("Selecting the zero width height DIV");
let zeroWidthHeight = getNode("#widthHeightZero-div");
onBoxModelUpdate = waitForBoxModelUpdate();
yield selectAndHighlightNode(zeroWidthHeight, inspector);
yield onBoxModelUpdate;
testMouseOverWidthHeightZeroDiv(zeroWidthHeight);
yield selectAndHighlightNode("#widthHeightZero-div", inspector);
isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#widthHeightZero-div"), toolbox,
"zero width height");
});
function testSimpleDivHighlighted(div) {
ok(isHighlighting(), "The highlighter is shown");
is(getHighlitNode(), div, "The highlighter's outline corresponds to the simple div");
info("Checking that the simple div is correctly highlighted");
isNodeCorrectlyHighlighted(div, "non-zoomed");
}
function testZoomedSimpleDivHighlighted(div) {
info("Checking that the simple div is correctly highlighted, " +
"even when the page is zoomed");
isNodeCorrectlyHighlighted(div, "zoomed");
}
function zoomTo(level) {
info("Zooming page to " + level);
let def = promise.defer();
waitForBoxModelUpdate().then(def.resolve);
let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer;
contentViewer.fullZoom = level;
return def.promise;
}
function testMouseOverRotatedHighlights(rotated) {
ok(isHighlighting(), "The highlighter is shown");
info("Checking that the rotated div is correctly highlighted");
isNodeCorrectlyHighlighted(rotated, "rotated");
}
function testMouseOverWidthHeightZeroDiv(zeroHeightWidthDiv) {
ok(isHighlighting(), "The highlighter is shown");
info("Checking that the zero width height div is correctly highlighted");
isNodeCorrectlyHighlighted(zeroHeightWidthDiv, "zero width height");
}

View File

@ -24,20 +24,19 @@ const DOCUMENT_SRC = "<style>" +
"}" +
"</style>" +
"<body>" +
"<iframe src='data:text/html," + IFRAME_SRC + "'></iframe>" +
"<iframe src='data:text/html;charset=utf-8," + IFRAME_SRC + "'></iframe>" +
"</body>";
const TEST_URI = "data:text/html;charset=utf-8," + DOCUMENT_SRC;
let test = asyncTest(function* () {
add_task(function* () {
let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
let outerDocument = content.document;
let iframeNode = getNode("iframe");
let iframeBodyNode = getNode("body", { document: iframeNode.contentDocument });
info("Waiting for box mode to show.");
yield toolbox.highlighter.showBoxModel(getNodeFront(outerDocument.body));
let body = yield getNodeFront("body", inspector);
yield toolbox.highlighter.showBoxModel(body);
info("Waiting for element picker to become active.");
yield toolbox.highlighterUtils.startPicker();
@ -46,25 +45,28 @@ let test = asyncTest(function* () {
yield moveMouseOver(iframeNode, 1, 1);
info("Performing checks");
isNodeCorrectlyHighlighted(iframeNode);
yield isNodeCorrectlyHighlighted(iframeNode, toolbox);
info("Scrolling the document");
iframeNode.style.marginBottom = outerDocument.defaultView.innerHeight + "px";
outerDocument.defaultView.scrollBy(0, 40);
iframeNode.style.marginBottom = content.innerHeight + "px";
content.scrollBy(0, 40);
let iframeBodyNode = iframeNode.contentDocument.body;
info("Moving mouse over iframe body");
yield moveMouseOver(iframeNode, 40, 40);
is(getHighlitNode(), iframeBodyNode, "highlighter shows the right node");
isNodeCorrectlyHighlighted(iframeBodyNode);
let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, iframeBodyNode, "highlighter shows the right node");
yield isNodeCorrectlyHighlighted(iframeBodyNode, toolbox);
info("Waiting for the element picker to deactivate.");
yield inspector.toolbox.highlighterUtils.stopPicker();
function moveMouseOver(aElement, x, y) {
info("Waiting for element " + aElement + " to be highlighted");
EventUtils.synthesizeMouse(aElement, x, y, {type: "mousemove"},
aElement.ownerDocument.defaultView);
function moveMouseOver(node, x, y) {
info("Waiting for element " + node + " to be highlighted");
executeInContent("Test:SynthesizeMouse", {x, y, type: "mousemove"},
{node}, false);
return inspector.toolbox.once("picker-node-hovered");
}
});

View File

@ -0,0 +1,48 @@
/* 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/. */
"use strict";
// Check that various highlighter elements exist.
const TEST_URL = "data:text/html;charset=utf-8,<div>test</div>";
// IDs of all highlighter elements that we expect to find in the canvasFrame.
const ELEMENTS = ["box-model-root",
"box-model-margin",
"box-model-border",
"box-model-padding",
"box-model-content",
"box-model-guide-top",
"box-model-guide-right",
"box-model-guide-bottom",
"box-model-guide-left",
"box-model-nodeinfobar-container",
"box-model-nodeinfobar-tagname",
"box-model-nodeinfobar-id",
"box-model-nodeinfobar-classes",
"box-model-nodeinfobar-pseudo-classes",
"box-model-nodeinfobar-dimensions"];
add_task(function*() {
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
info("Show the box-model highlighter");
let divFront = yield getNodeFront("div", inspector);
yield toolbox.highlighter.showBoxModel(divFront);
for (let id of ELEMENTS) {
let {data: foundId} = yield executeInContent("Test:GetHighlighterAttribute", {
nodeID: id,
name: "id",
actorID: getHighlighterActorID(toolbox)
});
is(foundId, id, "Element " + id + " found");
}
info("Hide the box-model highlighter");
yield toolbox.highlighter.hideBoxModel();
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,68 @@
/* 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/. */
"use strict";
// Check that custom highlighters can be retrieved by type and that they expose
// the expected API.
const TEST_URL = "data:text/html;charset=utf-8,custom highlighters";
add_task(function*() {
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
yield onlyOneInstanceOfMainHighlighter(inspector);
yield manyInstancesOfCustomHighlighters(inspector);
yield showHideMethodsAreAvailable(inspector);
yield unknownHighlighterTypeShouldntBeAccepted(inspector);
gBrowser.removeCurrentTab();
});
function* onlyOneInstanceOfMainHighlighter({inspector}) {
info("Check that the inspector always sends back the same main highlighter");
let h1 = yield inspector.getHighlighter(false);
let h2 = yield inspector.getHighlighter(false);
is(h1, h2, "The same highlighter front was returned");
is(h1.typeName, "highlighter", "The right front type was returned");
}
function* manyInstancesOfCustomHighlighters({inspector}) {
let h1 = yield inspector.getHighlighterByType("BoxModelHighlighter");
let h2 = yield inspector.getHighlighterByType("BoxModelHighlighter");
ok(h1 !== h2, "getHighlighterByType returns new instances every time (1)");
let h3 = yield inspector.getHighlighterByType("CssTransformHighlighter");
let h4 = yield inspector.getHighlighterByType("CssTransformHighlighter");
ok(h3 !== h4, "getHighlighterByType returns new instances every time (2)");
ok(h3 !== h1 && h3 !== h2,
"getHighlighterByType returns new instances every time (3)");
ok(h4 !== h1 && h4 !== h2,
"getHighlighterByType returns new instances every time (4)");
yield h1.finalize();
yield h2.finalize();
yield h3.finalize();
yield h4.finalize();
}
function* showHideMethodsAreAvailable({inspector}) {
let h1 = yield inspector.getHighlighterByType("BoxModelHighlighter");
let h2 = yield inspector.getHighlighterByType("CssTransformHighlighter");
ok("show" in h1, "Show method is present on the front API");
ok("show" in h2, "Show method is present on the front API");
ok("hide" in h1, "Hide method is present on the front API");
ok("hide" in h2, "Hide method is present on the front API");
yield h1.finalize();
yield h2.finalize();
}
function* unknownHighlighterTypeShouldntBeAccepted({inspector}) {
let h = yield inspector.getHighlighterByType("whatever");
ok(!h, "No highlighter was returned for the invalid type");
}

View File

@ -18,38 +18,38 @@ thisTestLeaksUncaughtRejectionsAndShouldBeFixed("false");
const TEST_PAGE = TEST_URL_ROOT +
"doc_inspector_highlighter-comments.html";
let test = asyncTest(function* () {
let { inspector } = yield openInspectorForURL(TEST_PAGE);
add_task(function* () {
let {toolbox, inspector} = yield openInspectorForURL(TEST_PAGE);
let markupView = inspector.markup;
yield selectNode("p", inspector);
info("Hovering over #id1 and waiting for highlighter to appear.");
yield hoverElement("#id1");
assertHighlighterShownOn("#id1");
yield assertHighlighterShownOn("#id1");
info("Hovering over comment node and ensuring highlighter doesn't appear.");
yield hoverComment();
assertHighlighterHidden();
yield assertHighlighterHidden();
info("Hovering over #id1 again and waiting for highlighter to appear.");
yield hoverElement("#id1");
assertHighlighterShownOn("#id1");
yield assertHighlighterShownOn("#id1");
info("Hovering over #id2 and waiting for highlighter to appear.");
yield hoverElement("#id2");
assertHighlighterShownOn("#id2");
yield assertHighlighterShownOn("#id2");
info("Hovering over <script> and ensuring highlighter doesn't appear.");
yield hoverElement("script");
assertHighlighterHidden();
yield assertHighlighterHidden();
info("Hovering over #id3 and waiting for highlighter to appear.");
yield hoverElement("#id3");
assertHighlighterShownOn("#id3");
yield assertHighlighterShownOn("#id3");
info("Hovering over hidden #id4 and ensuring highlighter doesn't appear.");
yield hoverElement("#id4");
assertHighlighterHidden();
yield assertHighlighterHidden();
function hoverContainer(container) {
let promise = inspector.toolbox.once("node-highlight");
@ -59,9 +59,9 @@ let test = asyncTest(function* () {
return promise;
}
function hoverElement(selector) {
function* hoverElement(selector) {
info("Hovering node " + selector + " in the markup view");
let container = getContainerForRawNode(markupView, getNode(selector));
let container = yield getContainerForSelector(selector, inspector);
return hoverContainer(container);
}
@ -74,13 +74,14 @@ let test = asyncTest(function* () {
}
}
function assertHighlighterShownOn(selector) {
function* assertHighlighterShownOn(selector) {
let node = getNode(selector);
let highlightNode = getHighlitNode();
let highlightNode = yield getHighlitNode(toolbox);
is(node, highlightNode, "Highlighter is shown on the right node: " + selector);
}
function assertHighlighterHidden() {
ok(!isHighlighting(), "Highlighter is hidden");
function* assertHighlighterHidden() {
let isVisible = yield isHighlighting(toolbox);
ok(!isVisible, "Highlighter is hidden");
}
});

View File

@ -0,0 +1,134 @@
/* 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/. */
"use strict";
// Test the creation of the SVG highlighter elements of the css transform
// highlighter.
const TEST_URL = "data:text/html;charset=utf-8," +
"<div id='transformed' style='border:1px solid red;width:100px;height:100px;transform:skew(13deg);'></div>" +
"<div id='untransformed' style='border:1px solid blue;width:100px;height:100px;'></div>" +
"<span id='inline' style='transform:rotate(90deg);'>this is an inline transformed element</span>";
add_task(function*() {
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
let front = inspector.inspector;
let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
yield isHiddenByDefault(highlighter, inspector);
yield has2PolygonsAnd4Lines(highlighter, inspector);
yield isNotShownForUntransformed(highlighter, inspector);
yield isNotShownForInline(highlighter, inspector);
yield isVisibleWhenShown(highlighter, inspector);
yield linesLinkThePolygons(highlighter, inspector);
yield highlighter.finalize();
gBrowser.removeCurrentTab();
});
function* isHiddenByDefault(highlighterFront, inspector) {
info("Checking that the highlighter is hidden by default");
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
ok(hidden, "The highlighter is hidden by default");
}
function* has2PolygonsAnd4Lines(highlighterFront, inspector) {
info("Checking that the highlighter is made up of 4 lines and 2 polygons");
let value = yield getAttribute("css-transform-untransformed", "class", highlighterFront);
is(value, "css-transform-untransformed", "The untransformed polygon exists");
value = yield getAttribute("css-transform-transformed", "class", highlighterFront);
is(value, "css-transform-transformed", "The transformed polygon exists");
for (let nb of ["1", "2", "3", "4"]) {
value = yield getAttribute("css-transform-line" + nb, "class", highlighterFront);
is(value, "css-transform-line", "The line " + nb + " exists");
}
}
function* isNotShownForUntransformed(highlighterFront, inspector) {
info("Asking to show the highlighter on the untransformed test node");
let node = yield getNodeFront("#untransformed", inspector);
yield highlighterFront.show(node);
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
ok(hidden, "The highlighter is still hidden");
}
function* isNotShownForInline(highlighterFront, inspector) {
info("Asking to show the highlighter on the inline test node");
let node = yield getNodeFront("#inline", inspector);
yield highlighterFront.show(node);
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
ok(hidden, "The highlighter is still hidden");
}
function* isVisibleWhenShown(highlighterFront, inspector) {
info("Asking to show the highlighter on the test node");
let node = yield getNodeFront("#transformed", inspector);
yield highlighterFront.show(node);
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
ok(!hidden, "The highlighter is visible");
info("Hiding the highlighter");
yield highlighterFront.hide();
hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
ok(hidden, "The highlighter is hidden");
}
function* linesLinkThePolygons(highlighterFront, inspector) {
info("Showing the highlighter on the transformed node");
let node = yield getNodeFront("#transformed", inspector);
yield highlighterFront.show(node);
info("Checking that the 4 lines do link the 2 shape's corners");
let lines = [];
for (let nb of ["1", "2", "3", "4"]) {
let x1 = yield getAttribute("css-transform-line" + nb, "x1", highlighterFront);
let y1 = yield getAttribute("css-transform-line" + nb, "y1", highlighterFront);
let x2 = yield getAttribute("css-transform-line" + nb, "x2", highlighterFront);
let y2 = yield getAttribute("css-transform-line" + nb, "y2", highlighterFront);
lines.push({x1, y1, x2, y2});
}
let points1 = yield getAttribute("css-transform-untransformed", "points", highlighterFront);
points1 = points1.split(" ");
let points2 = yield getAttribute("css-transform-transformed", "points", highlighterFront);
points2 = points2.split(" ");
for (let i = 0; i < lines.length; i++) {
info("Checking line nb " + i);
let line = lines[i];
let p1 = points1[i].split(",");
is(p1[0], line.x1, "line " + i + "'s first point matches the untransformed x coordinate");
is(p1[1], line.y1, "line " + i + "'s first point matches the untransformed y coordinate");
let p2 = points2[i].split(",");
is(p2[0], line.x2, "line " + i + "'s first point matches the transformed x coordinate");
is(p2[1], line.y2, "line " + i + "'s first point matches the transformed y coordinate");
}
yield highlighterFront.hide();
}
function* getAttribute(nodeID, name, {actorID}) {
let {data} = yield executeInContent("Test:GetHighlighterAttribute",
{nodeID, name, actorID});
return data;
}

View File

@ -0,0 +1,64 @@
/* 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/. */
"use strict";
/*
Bug 1014547 - CSS transforms highlighter
Test that the highlighter elements created have the right size and coordinates.
Note that instead of hard-coding values here, the assertions are made by
comparing with the result of LayoutHelpers.getAdjustedQuads.
There's a separate test for checking that getAdjustedQuads actually returns
sensible values
(browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js),
so the present test doesn't care about that, it just verifies that the css
transform highlighter applies those values correctly to the SVG elements
*/
const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_csstransform.html";
add_task(function*() {
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
let front = inspector.inspector;
let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
let node = getNode("#test-node");
let nodeFront = yield getNodeFront("#test-node", inspector);
info("Displaying the transform highlighter on test node");
yield highlighter.show(nodeFront);
let {data} = yield executeInContent("Test:GetAllAdjustedQuads", null, {node});
let expected = data.border;
let points = yield getAttribute("css-transform-transformed", "points", highlighter);
let polygonPoints = points.split(" ").map(p => {
return {
x: +p.substring(0, p.indexOf(",")),
y: +p.substring(p.indexOf(",")+1)
};
});
for (let i = 1; i < 5; i ++) {
is(polygonPoints[i - 1].x, expected["p" + i].x,
"p" + i + " x coordinate is correct");
is(polygonPoints[i - 1].y, expected["p" + i].y,
"p" + i + " y coordinate is correct");
}
info("Hiding the transform highlighter");
yield highlighter.hide();
yield highlighter.finalize();
gBrowser.removeCurrentTab();
});
function* getAttribute(nodeID, name, {actorID}) {
let {data} = yield executeInContent("Test:GetHighlighterAttribute",
{nodeID, name, actorID});
return data;
}

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