Merge m-c to inbound. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2014-11-03 17:11:06 -05:00
commit 0884e151e6
133 changed files with 8966 additions and 2134 deletions

View File

@ -60,22 +60,6 @@ const CAPTURE_LOGS_START_EVENT = 'capture-logs-start';
const CAPTURE_LOGS_ERROR_EVENT = 'capture-logs-error';
const CAPTURE_LOGS_SUCCESS_EVENT = 'capture-logs-success';
// Map of files which have log-type information to their parsers
const LOGS_WITH_PARSERS = {
'/dev/__properties__': LogParser.prettyPrintPropertiesArray,
'/dev/log/main': LogParser.prettyPrintLogArray,
'/dev/log/system': LogParser.prettyPrintLogArray,
'/dev/log/radio': LogParser.prettyPrintLogArray,
'/dev/log/events': LogParser.prettyPrintLogArray,
'/proc/cmdline': LogParser.prettyPrintArray,
'/proc/kmsg': LogParser.prettyPrintArray,
'/proc/meminfo': LogParser.prettyPrintArray,
'/proc/uptime': LogParser.prettyPrintArray,
'/proc/version': LogParser.prettyPrintArray,
'/proc/vmallocinfo': LogParser.prettyPrintArray,
'/proc/vmstat': LogParser.prettyPrintArray
};
let LogShake = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
/**
@ -91,6 +75,24 @@ let LogShake = {
*/
captureRequested: false,
/**
* Map of files which have log-type information to their parsers
*/
LOGS_WITH_PARSERS: {
'/dev/__properties__': LogParser.prettyPrintPropertiesArray,
'/dev/log/main': LogParser.prettyPrintLogArray,
'/dev/log/system': LogParser.prettyPrintLogArray,
'/dev/log/radio': LogParser.prettyPrintLogArray,
'/dev/log/events': LogParser.prettyPrintLogArray,
'/proc/cmdline': LogParser.prettyPrintArray,
'/proc/kmsg': LogParser.prettyPrintArray,
'/proc/meminfo': LogParser.prettyPrintArray,
'/proc/uptime': LogParser.prettyPrintArray,
'/proc/version': LogParser.prettyPrintArray,
'/proc/vmallocinfo': LogParser.prettyPrintArray,
'/proc/vmstat': LogParser.prettyPrintArray
},
/**
* Start existing, observing motion events if the screen is turned on.
*/
@ -169,7 +171,7 @@ let LogShake = {
if (!this.captureRequested) {
this.captureRequested = true;
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_START_EVENT, {});
captureLogs().then(logResults => {
this.captureLogs().then(logResults => {
// On resolution send the success event to the requester
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
logFilenames: logResults.logFilenames,
@ -194,6 +196,42 @@ let LogShake = {
}
},
/**
* Captures and saves the current device logs, returning a promise that will
* resolve to an array of log filenames.
*/
captureLogs: function() {
let logArrays = this.readLogs();
return saveLogs(logArrays);
},
/**
* Read in all log files, returning their formatted contents
*/
readLogs: function() {
let logArrays = {};
for (let loc in this.LOGS_WITH_PARSERS) {
let logArray;
try {
logArray = LogCapture.readLogFile(loc);
if (!logArray) {
continue;
}
} catch (ex) {
Cu.reportError("Unable to LogCapture.readLogFile('" + loc + "'): " + ex);
continue;
}
try {
logArrays[loc] = this.LOGS_WITH_PARSERS[loc](logArray);
} catch (ex) {
Cu.reportError("Unable to parse content of '" + loc + "': " + ex);
continue;
}
}
return logArrays;
},
/**
* Stop logshake, removing all listeners
*/
@ -225,32 +263,6 @@ function getLogDirectory() {
return OS.Path.join('logs', timestamp);
}
/**
* Captures and saves the current device logs, returning a promise that will
* resolve to an array of log filenames.
*/
function captureLogs() {
let logArrays = readLogs();
return saveLogs(logArrays);
}
/**
* Read in all log files, returning their formatted contents
*/
function readLogs() {
let logArrays = {};
for (let loc in LOGS_WITH_PARSERS) {
let logArray = LogCapture.readLogFile(loc);
if (!logArray) {
continue;
}
let prettyLogArray = LOGS_WITH_PARSERS[loc](logArray);
logArrays[loc] = prettyLogArray;
}
return logArrays;
}
/**
* Save the formatted arrays of log files to an sdcard if available
*/

View File

@ -135,6 +135,49 @@ add_test(function test_do_nothing_when_screen_off() {
run_next_test();
});
add_test(function test_do_log_capture_resilient_readLogFile() {
// Enable LogShake
LogShake.init();
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
throw new Error("Exception during readLogFile for: " + loc);
};
// Fire a devicemotion event that is of shake magnitude
sendDeviceMotionEvent(9001, 9001, 9001);
ok(readLocations.length > 0,
'LogShake should attempt to read at least one log');
LogShake.uninit();
run_next_test();
});
add_test(function test_do_log_capture_resilient_parseLog() {
// Enable LogShake
LogShake.init();
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
LogShake.LOGS_WITH_PARSERS[loc] = function() {
throw new Error("Exception during LogParser for: " + loc);
};
return null;
};
// Fire a devicemotion event that is of shake magnitude
sendDeviceMotionEvent(9001, 9001, 9001);
ok(readLocations.length > 0,
'LogShake should attempt to read at least one log');
LogShake.uninit();
run_next_test();
});
function run_test() {
debug('Starting');
run_next_test();

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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "62e4e9a9d7d888e136a1204406221c7df22be7c1",
"revision": "6bf7a21e0dbf766076bfd1c15e59dea7f25a4c13",
"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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

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="bc168c17474dabbcceaa349e9bc7c95654435aec"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
<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

@ -14,10 +14,11 @@ let DevEdition = {
styleSheetLocation: "chrome://browser/skin/devedition.css",
styleSheet: null,
defaultThemeID: "{972ce4c6-7e08-4474-a285-3208198ce6fd}",
init: function () {
this._updateDevtoolsThemeAttribute();
this._updateStyleSheet();
this._updateStyleSheetFromPrefs();
// Listen for changes to all prefs except for complete themes.
// No need for this since changing a complete theme requires a
@ -25,14 +26,27 @@ let DevEdition = {
Services.prefs.addObserver(this._lwThemePrefName, this, false);
Services.prefs.addObserver(this._prefName, this, false);
Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
},
observe: function (subject, topic, data) {
if (topic == "lightweight-theme-styling-update") {
let newTheme = JSON.parse(data);
if (!newTheme || newTheme.id === this.defaultThemeID) {
// A lightweight theme has been unapplied, so just re-read prefs.
this._updateStyleSheetFromPrefs();
} else {
// A lightweight theme has been applied, but the pref may not be
// set yet if this happened from customize menu or addons page.
this._toggleStyleSheet(false);
}
}
if (topic == "nsPref:changed") {
if (data == this._devtoolsThemePrefName) {
this._updateDevtoolsThemeAttribute();
} else {
this._updateStyleSheet();
this._updateStyleSheetFromPrefs();
}
}
},
@ -42,11 +56,10 @@ let DevEdition = {
// to change colors based on the selected devtools theme.
document.documentElement.setAttribute("devtoolstheme",
Services.prefs.getCharPref(this._devtoolsThemePrefName));
this._updateStyleSheetFromPrefs();
},
_updateStyleSheet: function() {
// Only try to apply the dev edition theme if it is preffered
// on and there are no other themes applied.
_updateStyleSheetFromPrefs: function() {
let lightweightThemeSelected = false;
try {
lightweightThemeSelected = Services.prefs.getBoolPref(this._lwThemePrefName);
@ -57,21 +70,37 @@ let DevEdition = {
defaultThemeSelected = Services.prefs.getCharPref(this._themePrefName) == "classic/1.0";
} catch(e) {}
let deveditionThemeEnabled = Services.prefs.getBoolPref(this._prefName) &&
!lightweightThemeSelected && defaultThemeSelected;
let devtoolsIsDark = false;
try {
devtoolsIsDark = Services.prefs.getCharPref(this._devtoolsThemePrefName) == "dark";
} catch(e) {}
let deveditionThemeEnabled = Services.prefs.getBoolPref(this._prefName) &&
!lightweightThemeSelected && defaultThemeSelected && devtoolsIsDark;
this._toggleStyleSheet(deveditionThemeEnabled);
},
handleEvent: function(e) {
if (e.type === "load") {
this.styleSheet.removeEventListener("load", this);
gBrowser.tabContainer._positionPinnedTabs();
ToolbarIconColor.inferFromText();
}
},
_toggleStyleSheet: function(deveditionThemeEnabled) {
if (deveditionThemeEnabled && !this.styleSheet) {
let styleSheetAttr = `href="${this.styleSheetLocation}" type="text/css"`;
let styleSheet = this.styleSheet = document.createProcessingInstruction(
this.styleSheet = document.createProcessingInstruction(
'xml-stylesheet', styleSheetAttr);
this.styleSheet.addEventListener("load", function onLoad() {
styleSheet.removeEventListener("load", onLoad);
ToolbarIconColor.inferFromText();
});
this.styleSheet.addEventListener("load", this);
document.insertBefore(this.styleSheet, document.documentElement);
} else if (!deveditionThemeEnabled && this.styleSheet) {
this.styleSheet.removeEventListener("load", this);
this.styleSheet.remove();
this.styleSheet = null;
gBrowser.tabContainer._positionPinnedTabs();
ToolbarIconColor.inferFromText();
}
},
@ -80,6 +109,10 @@ let DevEdition = {
Services.prefs.removeObserver(this._lwThemePrefName, this);
Services.prefs.removeObserver(this._prefName, this);
Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
if (this.styleSheet) {
this.styleSheet.removeEventListener("load", this);
}
this.styleSheet = null;
}
};

View File

@ -108,7 +108,7 @@ support-files =
[browser_aboutHealthReport.js]
skip-if = os == "linux" # Bug 924307
[browser_aboutHome.js]
skip-if = e10s # Bug ?????? - no about:home support yet
skip-if = e10s # Bug 1093153 - no about:home support yet
[browser_aboutSyncProgress.js]
[browser_action_keyword.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
@ -150,7 +150,7 @@ skip-if = e10s
[browser_bug416661.js]
skip-if = e10s # Bug 691614 - no e10s zoom support yet
[browser_bug417483.js]
skip-if = e10s # Bug ?????? - no about:home support yet
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
[browser_bug419612.js]
skip-if = e10s # Bug 691614 - no e10s zoom support yet
[browser_bug422590.js]
@ -178,7 +178,7 @@ skip-if = toolkit == "cocoa" || e10s # Bug ?????? - not sure why this is timing
[browser_bug462673.js]
skip-if = e10s # Bug 924260 - "Window is closed"
[browser_bug477014.js]
skip-if = e10s # Bug 918634 - swapFrameLoaders not implemented for e10s
skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s
[browser_bug479408.js]
skip-if = buildapp == 'mulet' || e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
[browser_bug481560.js]
@ -188,7 +188,7 @@ skip-if = e10s
[browser_bug491431.js]
skip-if = buildapp == 'mulet'
[browser_bug495058.js]
skip-if = e10s # Bug 918634 - swapFrameLoaders (and thus replaceTabWithWindow) not implemented for e10s
skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al (and thus replaceTabWithWindow) for e10s
[browser_bug517902.js]
skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
[browser_bug519216.js]
@ -198,7 +198,7 @@ skip-if = e10s # Bug ?????? - some weird timing issue with progress listeners th
skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
[browser_bug533232.js]
[browser_bug537013.js]
skip-if = buildapp == 'mulet' || e10s # Bug 918634 - swapFrameLoaders not implemented for e10s (test calls replaceTabWithWindow)
skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls replaceTabWithWindow)
[browser_bug537474.js]
skip-if = e10s # Bug ?????? - test doesn't wait for document to be created before it checks it
[browser_bug550565.js]
@ -429,13 +429,13 @@ skip-if = e10s # Bug ?????? - timeout after logging "Error: Channel closing: too
[browser_tabDrop.js]
skip-if = buildapp == 'mulet' || e10s
[browser_tabMatchesInAwesomebar_perwindowpb.js]
skip-if = e10s # Bug 918634 - swapFrameLoaders not implemented for e10s (test uses gBrowser.swapBrowsersAndCloseOther)
skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
[browser_tab_drag_drop_perwindow.js]
skip-if = buildapp == 'mulet'
[browser_tab_dragdrop.js]
skip-if = buildapp == 'mulet' || e10s # Bug 918634 - swapFrameLoaders not implemented for e10s (test uses gBrowser.swapBrowsersAndCloseOther)
skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
[browser_tab_dragdrop2.js]
skip-if = buildapp == 'mulet' || e10s
skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
[browser_tabbar_big_widgets.js]
skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
# Disabled on OS X because of bug 967917

View File

@ -6,14 +6,12 @@
*/
const PREF_DEVEDITION_THEME = "browser.devedition.theme.enabled";
const PREF_THEME = "general.skins.selectedSkin";
const PREF_LWTHEME = "lightweightThemes.isThemeSelected";
const PREF_DEVTOOLS_THEME = "devtools.theme";
registerCleanupFunction(() => {
// Set preferences back to their original values
Services.prefs.clearUserPref(PREF_DEVEDITION_THEME);
Services.prefs.clearUserPref(PREF_THEME);
Services.prefs.clearUserPref(PREF_LWTHEME);
Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
});
@ -24,6 +22,8 @@ function test() {
}
function startTests() {
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
info ("Setting browser.devedition.theme.enabled to false.");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
ok (!DevEdition.styleSheet, "There is no devedition style sheet when the pref is false.");
@ -40,30 +40,83 @@ function startTests() {
Services.prefs.setBoolPref(PREF_LWTHEME, false);
ok (DevEdition.styleSheet, "The devedition stylesheet has been added when a lightweight theme is removed.");
// There are no listeners for the complete theme pref since applying the theme
// requires a restart.
info ("Setting general.skins.selectedSkin to a custom string.");
Services.prefs.setCharPref(PREF_THEME, "custom-theme");
ok (DevEdition.styleSheet, "The devedition stylesheet is still here when a complete theme is added.");
info ("Resetting general.skins.selectedSkin to default value.");
Services.prefs.clearUserPref(PREF_THEME);
ok (DevEdition.styleSheet, "The devedition stylesheet is still here when a complete theme is removed.");
info ("Setting browser.devedition.theme.enabled to false.");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed.");
info ("Checking :root attributes based on devtools theme.");
testDevtoolsTheme();
testLightweightThemePreview();
finish();
}
function testDevtoolsTheme() {
info ("Checking that Australis is shown when the light devtools theme is applied.");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
ok (DevEdition.styleSheet, "The devedition stylesheet exists.");
info ("Checking stylesheet and :root attributes based on devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
is (document.documentElement.getAttribute("devtoolstheme"), "light",
"The documentElement has an attribute based on devtools theme.");
ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed because of light devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
is (document.documentElement.getAttribute("devtoolstheme"), "dark",
"The documentElement has an attribute based on devtools theme.");
ok (DevEdition.styleSheet, "The devedition stylesheet has been readded because of dark devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
is (document.documentElement.getAttribute("devtoolstheme"), "light",
"The documentElement has an attribute based on devtools theme.");
ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed because of light devtools theme.");
finish();
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
is (document.documentElement.getAttribute("devtoolstheme"), "dark",
"The documentElement has an attribute based on devtools theme.");
ok (DevEdition.styleSheet, "The devedition stylesheet has been readded because of dark devtools theme.");
}
function dummyLightweightTheme(id) {
return {
id: id,
name: id,
headerURL: "http://lwttest.invalid/a.png",
footerURL: "http://lwttest.invalid/b.png",
textcolor: "red",
accentcolor: "blue"
};
}
function testLightweightThemePreview() {
let {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
info ("Turning the pref on, then previewing lightweight themes");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a lightweight theme preview.");
LightweightThemeManager.resetPreview();
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a second lightweight theme preview.");
LightweightThemeManager.resetPreview();
ok (DevEdition.styleSheet, "The devedition stylesheet is enabled again after resetting the preview.");
info ("Turning the pref on, then previewing a theme, turning it off and resetting the preview");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a lightweight theme preview.");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after pref is turned off.");
LightweightThemeManager.resetPreview();
ok (!DevEdition.styleSheet, "The devedition stylesheet is still disabled after resetting the preview.");
info ("Turning the pref on, then previewing the default theme, turning it off and resetting the preview");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
LightweightThemeManager.previewTheme(dummyLightweightTheme("{972ce4c6-7e08-4474-a285-3208198ce6fd}"));
ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after the default theme is applied.");
LightweightThemeManager.resetPreview();
ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after resetting the preview.");
}

View File

@ -2,5 +2,5 @@
# 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/.
MOZ_APP_DISPLAYNAME=FirefoxDevEdition
MOZ_APP_DISPLAYNAME=FirefoxDeveloperEdition
MOZ_APP_REMOTINGNAME=firefox-dev

View File

@ -2,7 +2,7 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY brandShortName "FirefoxDevEdition">
<!ENTITY brandShortName "Firefox Developer Edition">
<!ENTITY brandFullName "Firefox Developer Edition">
<!ENTITY vendorShortName "Mozilla">
<!ENTITY trademarkInfo.part1 " ">

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
brandShortName=FirefoxDevEdition
brandShortName=Firefox Developer Edition
brandFullName=Firefox Developer Edition
vendorShortName=Mozilla

View File

@ -47,7 +47,7 @@ CallProgressSocket.prototype = {
connect: function(onSuccess, onError) {
this._onSuccess = onSuccess;
this._onError = onError ||
(reason => {MozLoopService.logwarn("LoopCalls::callProgessSocket - ", reason);});
(reason => {MozLoopService.log.warn("LoopCalls::callProgessSocket - ", reason);});
if (!onSuccess) {
this._onError("missing onSuccess argument");
@ -126,9 +126,8 @@ CallProgressSocket.prototype = {
let msg = {};
try {
msg = JSON.parse(aMsg);
}
catch (error) {
MozLoopService.logerror("LoopCalls: error parsing progress message - ", error);
} catch (error) {
MozLoopService.log.error("LoopCalls: error parsing progress message - ", error);
return;
}
@ -146,7 +145,7 @@ CallProgressSocket.prototype = {
*/
_send: function(aMsg) {
if (!this._handshakeComplete) {
MozLoopService.logwarn("LoopCalls::_send error - handshake not complete");
MozLoopService.log.warn("LoopCalls::_send error - handshake not complete");
return;
}
@ -179,14 +178,12 @@ CallProgressSocket.prototype = {
* and register with the Loop server.
*/
let LoopCallsInternal = {
callsData: {
inUse: false,
},
mocks: {
webSocket: undefined,
},
conversationInProgress: {},
/**
* Callback from MozLoopPushHandler - A push notification has been received from
* the server.
@ -248,20 +245,19 @@ let LoopCallsInternal = {
let respData = JSON.parse(response.body);
if (respData.calls && Array.isArray(respData.calls)) {
respData.calls.forEach((callData) => {
if (!this.callsData.inUse) {
callData.sessionType = sessionType;
// XXX Bug 1090209 will transiton into a better window id.
callData.windowId = callData.callId;
this._startCall(callData, "incoming");
} else {
if ("id" in this.conversationInProgress) {
this._returnBusy(callData);
} else {
callData.sessionType = sessionType;
callData.type = "incoming";
this._startCall(callData);
}
});
} else {
MozLoopService.logwarn("Error: missing calls[] in response");
MozLoopService.log.warn("Error: missing calls[] in response");
}
} catch (err) {
MozLoopService.logwarn("Error parsing calls info", err);
MozLoopService.log.warn("Error parsing calls info", err);
}
},
@ -269,17 +265,11 @@ let LoopCallsInternal = {
* Starts a call, saves the call data, and opens a chat window.
*
* @param {Object} callData The data associated with the call including an id.
* @param {Boolean} conversationType Whether or not the call is "incoming"
* or "outgoing"
* The data should include the type - "incoming" or
* "outgoing".
*/
_startCall: function(callData, conversationType) {
this.callsData.inUse = true;
this.callsData.data = callData;
MozLoopService.openChatWindow(
null,
// No title, let the page set that, to avoid flickering.
"",
"about:loopconversation#" + conversationType + "/" + callData.windowId);
_startCall: function(callData) {
this.conversationInProgress.id = MozLoopService.openChatWindow(callData);
},
/**
@ -290,17 +280,16 @@ let LoopCallsInternal = {
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: function(contact, callType) {
if (this.callsData.inUse)
if ("id" in this.conversationInProgress)
return false;
var callData = {
contact: contact,
callType: callType,
// XXX Really we shouldn't be using random numbers, bug 1090209 will fix this.
windowId: Math.floor((Math.random() * 100000000))
type: "outgoing"
};
this._startCall(callData, "outgoing");
this._startCall(callData);
return true;
},
@ -341,21 +330,18 @@ this.LoopCalls = {
},
/**
* Returns the callData for a specific conversation window id.
* Used to signify that a call is in progress.
*
* The data was retrieved from the LoopServer via a GET/calls/<version> request
* triggered by an incoming message from the LoopPushServer.
*
* @param {Number} conversationWindowId
* @return {callData} The callData or undefined if error.
* @param {String} The window id for the call in progress.
*/
getCallData: function(conversationWindowId) {
if (LoopCallsInternal.callsData.data &&
LoopCallsInternal.callsData.data.windowId == conversationWindowId) {
return LoopCallsInternal.callsData.data;
} else {
return undefined;
setCallInProgress: function(conversationWindowId) {
if ("id" in LoopCallsInternal.conversationInProgress &&
LoopCallsInternal.conversationInProgress.id != conversationWindowId) {
MozLoopService.log.error("Starting a new conversation when one is already in progress?");
return;
}
LoopCallsInternal.conversationInProgress.id = conversationWindowId;
},
/**
@ -365,11 +351,10 @@ this.LoopCalls = {
*
* @param {Number} conversationWindowId
*/
releaseCallData: function(conversationWindowId) {
if (LoopCallsInternal.callsData.data &&
LoopCallsInternal.callsData.data.windowId == conversationWindowId) {
LoopCallsInternal.callsData.data = undefined;
LoopCallsInternal.callsData.inUse = false;
clearCallInProgress: function(conversationWindowId) {
if ("id" in LoopCallsInternal.conversationInProgress &&
LoopCallsInternal.conversationInProgress.id == conversationWindowId) {
delete LoopCallsInternal.conversationInProgress.id;
}
},

View File

@ -41,6 +41,61 @@ const extend = function(target, source) {
return target;
};
/**
* Checks whether a participant is already part of a room.
*
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#User_Identification_in_a_Room
*
* @param {Object} room A room object that contains a list of current participants
* @param {Object} participant Participant to check if it's already there
* @returns {Boolean} TRUE when the participant is already a member of the room,
* FALSE when it's not.
*/
const containsParticipant = function(room, participant) {
for (let user of room.participants) {
if (user.roomConnectionId == participant.roomConnectionId) {
return true;
}
}
return false;
};
/**
* Compares the list of participants of the room currently in the cache and an
* updated version of that room. When a new participant is found, the 'joined'
* event is emitted. When a participant is not found in the update, it emits a
* 'left' event.
*
* @param {Object} room A room object to compare the participants list
* against
* @param {Object} updatedRoom A room object that contains the most up-to-date
* list of participants
*/
const checkForParticipantsUpdate = function(room, updatedRoom) {
// Partially fetched rooms don't contain the participants list yet. Skip the
// check for now.
if (!("participants" in room)) {
return;
}
let participant;
// Check for participants that joined.
for (participant of updatedRoom.participants) {
if (!containsParticipant(room, participant)) {
eventEmitter.emit("joined", room.roomToken, participant);
eventEmitter.emit("joined:" + room.roomToken, participant);
}
}
// Check for participants that left.
for (participant of room.participants) {
if (!containsParticipant(updatedRoom, participant)) {
eventEmitter.emit("left", room.roomToken, participant);
eventEmitter.emit("left:" + room.roomToken, participant);
}
}
};
/**
* The Rooms class.
*
@ -85,11 +140,23 @@ let LoopRoomsInternal = {
throw new Error("Missing array of rooms in response.");
}
// Next, request the detailed information for each room. If the request
// fails the room data will not be added to the map.
for (let room of roomsList) {
// See if we already have this room in our cache.
let orig = this.rooms.get(room.roomToken);
if (orig) {
checkForParticipantsUpdate(orig, room);
}
this.rooms.set(room.roomToken, room);
yield LoopRooms.promise("get", room.roomToken);
// When a version is specified, all the data is already provided by this
// request.
if (version) {
eventEmitter.emit("update", room);
eventEmitter.emit("update" + ":" + room.roomToken, room);
} else {
// Next, request the detailed information for each room. If the request
// fails the room data will not be added to the map.
yield LoopRooms.promise("get", room.roomToken);
}
}
// Set the 'dirty' flag back to FALSE, since the list is as fresh as can be now.
@ -113,25 +180,35 @@ let LoopRoomsInternal = {
get: function(roomToken, callback) {
let room = this.rooms.has(roomToken) ? this.rooms.get(roomToken) : {};
// Check if we need to make a request to the server to collect more room data.
if (!room || gDirty || !("participants" in room)) {
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
.then(response => {
let eventName = ("roomToken" in room) ? "add" : "update";
extend(room, JSON.parse(response.body));
// Remove the `currSize` for posterity.
if ("currSize" in room) {
delete room.currSize;
}
this.rooms.set(roomToken, room);
eventEmitter.emit(eventName, room);
callback(null, room);
}, err => callback(err)).catch(err => callback(err));
} else {
let needsUpdate = !("participants" in room);
if (!gDirty && !needsUpdate) {
// Dirty flag is not set AND the necessary data is available, so we can
// simply return the room.
callback(null, room);
return;
}
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
.then(response => {
let data = JSON.parse(response.body);
room.roomToken = roomToken;
checkForParticipantsUpdate(room, data);
extend(room, data);
// Remove the `currSize` for posterity.
if ("currSize" in room) {
delete room.currSize;
}
this.rooms.set(roomToken, room);
let eventName = !needsUpdate ? "update" : "add";
eventEmitter.emit(eventName, room);
eventEmitter.emit(eventName + ":" + roomToken, room);
callback(null, room);
}, err => callback(err)).catch(err => callback(err));
},
/**
@ -166,6 +243,15 @@ let LoopRoomsInternal = {
}, error => callback(error)).catch(error => callback(error));
},
open: function(roomToken) {
let windowData = {
roomToken: roomToken,
type: "room"
};
MozLoopService.openChatWindow(windowData);
},
/**
* Callback used to indicate changes to rooms data on the LoopServer.
*
@ -185,10 +271,13 @@ Object.freeze(LoopRoomsInternal);
* LoopRooms implements the EventEmitter interface by exposing three methods -
* `on`, `once` and `off` - to subscribe to events.
* At this point the following events may be subscribed to:
* - 'add': A new room object was successfully added to the data store.
* - 'remove': A room was successfully removed from the data store.
* - 'update': A room object was successfully updated with changed
* properties in the data store.
* - 'add[:{room-id}]': A new room object was successfully added to the data
* store.
* - 'remove[:{room-id}]': A room was successfully removed from the data store.
* - 'update[:{room-id}]': A room object was successfully updated with changed
* properties in the data store.
* - 'joined[:{room-id}]': A participant joined a room.
* - 'left[:{room-id}]': A participant left a room.
*
* See the internal code for the API documentation.
*/
@ -205,6 +294,10 @@ this.LoopRooms = {
return LoopRoomsInternal.create(options, callback);
},
open: function(roomToken) {
return LoopRoomsInternal.open(roomToken);
},
promise: function(method, ...params) {
return new Promise((resolve, reject) => {
this[method](...params, (error, result) => {

View File

@ -104,10 +104,21 @@ const injectObjectAPI = function(api, targetWindow) {
// through the priv => unpriv barrier with `Cu.cloneInto()`.
Object.keys(api).forEach(func => {
injectedAPI[func] = function(...params) {
let callback = params.pop();
api[func](...params, function(...results) {
callback(...[cloneValueInto(r, targetWindow) for (r of results)]);
});
let lastParam = params.pop();
// If the last parameter is a function, assume its a callback
// and wrap it differently.
if (lastParam && typeof lastParam === "function") {
api[func](...params, function(...results) {
lastParam(...[cloneValueInto(r, targetWindow) for (r of results)]);
});
} else {
try {
return cloneValueInto(api[func](...params, lastParam), targetWindow);
} catch (ex) {
return cloneValueInto(ex, targetWindow);
}
}
};
});
@ -135,6 +146,7 @@ function injectLoopAPI(targetWindow) {
let appVersionInfo;
let contactsAPI;
let roomsAPI;
let callsAPI;
let api = {
/**
@ -206,34 +218,20 @@ function injectLoopAPI(targetWindow) {
},
/**
* Returns the callData for a specific conversation window id.
* Returns the window data for a specific conversation window id.
*
* The data was retrieved from the LoopServer via a GET/calls/<version> request
* triggered by an incoming message from the LoopPushServer.
* This data will be relevant to the type of window, e.g. rooms or calls.
* See LoopRooms or LoopCalls for more information.
*
* @param {Number} conversationWindowId
* @returns {callData} The callData or undefined if error.
* @param {String} conversationWindowId
* @returns {Object} The window data or null if error.
*/
getCallData: {
getConversationWindowData: {
enumerable: true,
writable: true,
value: function(conversationWindowId) {
return Cu.cloneInto(LoopCalls.getCallData(conversationWindowId), targetWindow);
}
},
/**
* Releases the callData for a specific conversation window id.
*
* The result of this call will be a free call session slot.
*
* @param {Number} conversationWindowId
*/
releaseCallData: {
enumerable: true,
writable: true,
value: function(conversationWindowId) {
LoopCalls.releaseCallData(conversationWindowId);
return Cu.cloneInto(MozLoopService.getConversationWindowData(conversationWindowId),
targetWindow);
}
},
@ -273,6 +271,22 @@ function injectLoopAPI(targetWindow) {
}
},
/**
* Returns the calls API.
*
* @returns {Object} The rooms API object
*/
calls: {
enumerable: true,
get: function() {
if (callsAPI) {
return callsAPI;
}
return callsAPI = injectObjectAPI(LoopCalls, targetWindow);
}
},
/**
* Import a list of (new) contacts from an external data source.
*
@ -670,21 +684,6 @@ function injectLoopAPI(targetWindow) {
return MozLoopService.generateUUID();
}
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: {
enumerable: true,
writable: true,
value: function(contact, callType) {
LoopCalls.startDirectCall(contact, callType);
}
},
};
function onStatusChanged(aSubject, aTopic, aData) {

View File

@ -116,6 +116,8 @@ let gFxAEnabled = true;
let gFxAOAuthClientPromise = null;
let gFxAOAuthClient = null;
let gErrors = new Map();
let gLastWindowId = 0;
let gConversationWindowData = new Map();
/**
* Internal helper methods and state
@ -693,15 +695,20 @@ let MozLoopServiceInternal = {
/**
* Opens the chat window
*
* @param {Object} contentWindow The window to open the chat window in, may
* be null.
* @param {String} title The title of the chat window.
* @param {String} url The page to load in the chat window.
* @param {Object} conversationWindowData The data to be obtained by the
* window when it opens.
* @returns {Number} The id of the window.
*/
openChatWindow: function(contentWindow, title, url) {
openChatWindow: function(conversationWindowData) {
// So I guess the origin is the loop server!?
let origin = this.loopServerUri;
url = url.spec || url;
let windowId = gLastWindowId++;
// Store the id as a string, as that's what we use elsewhere.
windowId = windowId.toString();
gConversationWindowData.set(windowId, conversationWindowData);
let url = "about:loopconversation#" + windowId;
let callback = chatbox => {
// We need to use DOMContentLoaded as otherwise the injection will happen
@ -749,7 +756,8 @@ let MozLoopServiceInternal = {
}.bind(this), true);
};
Chat.open(contentWindow, origin, title, url, undefined, undefined, callback);
Chat.open(null, origin, "", url, undefined, undefined, callback);
return windowId;
},
/**
@ -996,13 +1004,12 @@ this.MozLoopService = {
/**
* Opens the chat window
*
* @param {Object} contentWindow The window to open the chat window in, may
* be null.
* @param {String} title The title of the chat window.
* @param {String} url The page to load in the chat window.
* @param {Object} conversationWindowData The data to be obtained by the
* window when it opens.
* @returns {Number} The id of the window.
*/
openChatWindow: function(contentWindow, title, url) {
MozLoopServiceInternal.openChatWindow(contentWindow, title, url);
openChatWindow: function(conversationWindowData) {
return MozLoopServiceInternal.openChatWindow(conversationWindowData);
},
/**
@ -1416,4 +1423,24 @@ this.MozLoopService = {
return MozLoopServiceInternal.hawkRequest(sessionType, path, method, payloadObj).catch(
error => {MozLoopServiceInternal._hawkRequestError(error);});
},
/**
* Returns the window data for a specific conversation window id.
*
* This data will be relevant to the type of window, e.g. rooms or calls.
* See LoopRooms or LoopCalls for more information.
*
* @param {String} conversationWindowId
* @returns {Object} The window data or null if error.
*/
getConversationWindowData: function(conversationWindowId) {
if (gConversationWindowData.has(conversationWindowId)) {
var conversationData = gConversationWindowData.get(conversationWindowId);
gConversationWindowData.delete(conversationWindowId);
return conversationData;
}
log.error("Window data was already fetched before. Possible race condition!");
return null;
}
};

View File

@ -38,6 +38,7 @@
<script type="text/javascript" src="loop/shared/js/localRoomStore.js"></script>
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
<script type="text/javascript" src="loop/js/conversationAppStore.js"></script>
<script type="text/javascript" src="loop/js/client.js"></script>
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
<script type="text/javascript" src="loop/js/roomViews.js"></script>

View File

@ -392,12 +392,12 @@ loop.contacts = (function(_, mozL10n) {
break;
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
}
break;
default:

View File

@ -392,12 +392,12 @@ loop.contacts = (function(_, mozL10n) {
break;
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
}
break;
default:

View File

@ -179,12 +179,12 @@ loop.conversation = (function(mozL10n) {
});
/**
* Incoming Call failed view. Displayed when a call fails.
* Something went wrong view. Displayed when there's a big problem.
*
* XXX Based on CallFailedView, but built specially until we flux-ify the
* incoming call views (bug 1088672).
*/
var IncomingCallFailedView = React.createClass({displayName: 'IncomingCallFailedView',
var GenericFailureView = React.createClass({displayName: 'GenericFailureView',
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
@ -218,7 +218,9 @@ loop.conversation = (function(mozL10n) {
client: React.PropTypes.instanceOf(loop.Client).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
conversationAppStore: React.PropTypes.instanceOf(
loop.store.ConversationAppStore).isRequired
},
getInitialState: function() {
@ -283,7 +285,7 @@ loop.conversation = (function(mozL10n) {
case "end": {
// XXX To be handled with the "failed" view state when bug 1047410 lands
if (this.state.callFailed) {
return IncomingCallFailedView({
return GenericFailureView({
cancelCall: this.closeWindow.bind(this)}
)
}
@ -352,13 +354,9 @@ loop.conversation = (function(mozL10n) {
setupIncomingCall: function() {
navigator.mozLoop.startAlerting();
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId"));
if (!callData) {
// XXX Not the ideal response, but bug 1047410 will be replacing
// this by better "call failed" UI.
console.error("Failed to get the call data");
return;
}
// XXX This is a hack until we rework for the flux model in bug 1088672.
var callData = this.props.conversationAppStore.getStoreState().windowData;
this.props.conversation.setIncomingSessionData(callData);
this._setupWebSocket();
},
@ -374,7 +372,8 @@ loop.conversation = (function(mozL10n) {
* Moves the call to the end state
*/
endCall: function() {
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
navigator.mozLoop.calls.clearCallInProgress(
this.props.conversation.get("windowId"));
this.setState({callStatus: "end"});
},
@ -475,7 +474,8 @@ loop.conversation = (function(mozL10n) {
*/
_declineCall: function() {
this._websocket.decline();
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
navigator.mozLoop.calls.clearCallInProgress(
this.props.conversation.get("windowId"));
this._websocket.close();
// Having a timeout here lets the logging for the websocket complete and be
// displayed on the console if both are on.
@ -523,6 +523,8 @@ loop.conversation = (function(mozL10n) {
* in progress, and hence, which view to display.
*/
var AppControllerView = React.createClass({displayName: 'AppControllerView',
mixins: [Backbone.Events],
propTypes: {
// XXX Old types required for incoming call view.
client: React.PropTypes.instanceOf(loop.Client).isRequired,
@ -530,51 +532,66 @@ loop.conversation = (function(mozL10n) {
.isRequired,
sdk: React.PropTypes.object.isRequired,
// XXX New types for OutgoingConversationView
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
// XXX New types for flux style
conversationAppStore: React.PropTypes.instanceOf(
loop.store.ConversationAppStore).isRequired,
conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore)
.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
// if not passed, this is not a room view
localRoomStore: React.PropTypes.instanceOf(loop.store.LocalRoomStore)
},
getInitialState: function() {
return this.props.store.attributes;
return this.props.conversationAppStore.getStoreState();
},
componentWillMount: function() {
this.props.store.on("change:outgoing", function() {
this.setState(this.props.store.attributes);
this.listenTo(this.props.conversationAppStore, "change", function() {
this.setState(this.props.conversationAppStore.getStoreState());
}, this);
},
componentWillUnmount: function() {
this.stopListening(this.props.conversationAppStore);
},
closeWindow: function() {
window.close();
},
render: function() {
if (this.props.localRoomStore) {
return (
EmptyRoomView({
switch(this.state.windowType) {
case "incoming": {
return (IncomingConversationView({
client: this.props.client,
conversation: this.props.conversation,
sdk: this.props.sdk,
conversationAppStore: this.props.conversationAppStore}
));
}
case "outgoing": {
return (OutgoingConversationView({
store: this.props.conversationStore,
dispatcher: this.props.dispatcher}
));
}
case "room": {
return (EmptyRoomView({
mozLoop: navigator.mozLoop,
localRoomStore: this.props.localRoomStore}
)
);
));
}
case "failed": {
return (GenericFailureView({
cancelCall: this.closeWindow}
));
}
default: {
// If we don't have a windowType, we don't know what we are yet,
// so don't display anything.
return null;
}
}
// Don't display anything, until we know what type of call we are.
if (this.state.outgoing === undefined) {
return null;
}
if (this.state.outgoing) {
return (OutgoingConversationView({
store: this.props.store,
dispatcher: this.props.dispatcher}
));
}
return (IncomingConversationView({
client: this.props.client,
conversation: this.props.conversation,
sdk: this.props.sdk}
));
}
});
@ -605,11 +622,20 @@ loop.conversation = (function(mozL10n) {
sdk: OT
});
// Create the stores.
var conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
var conversationStore = new loop.store.ConversationStore({}, {
client: client,
dispatcher: dispatcher,
sdkDriver: sdkDriver
});
var localRoomStore = new loop.store.LocalRoomStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});;
// XXX Old class creation for the incoming conversation view, whilst
// we transition across (bug 1072323).
@ -622,57 +648,31 @@ loop.conversation = (function(mozL10n) {
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationData().hash;
var windowId;
var outgoing;
var localRoomStore;
// XXX removeMe, along with noisy comment at the beginning of
// conversation_test.js "when locationHash begins with #room".
if (navigator.mozLoop.getLoopBoolPref("test.alwaysUseRooms")) {
locationHash = "#room/32";
}
var hash = locationHash.match(/#incoming\/(.*)/);
var hash = locationHash.match(/#(.*)/);
if (hash) {
windowId = hash[1];
outgoing = false;
} else if (hash = locationHash.match(/#room\/(.*)/)) {
localRoomStore = new loop.store.LocalRoomStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
} else {
hash = locationHash.match(/#outgoing\/(.*)/);
if (hash) {
windowId = hash[1];
outgoing = true;
}
}
conversation.set({windowId: windowId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(windowId);
navigator.mozLoop.calls.clearCallInProgress(windowId);
});
React.renderComponent(AppControllerView({
conversationAppStore: conversationAppStore,
localRoomStore: localRoomStore,
store: conversationStore,
conversationStore: conversationStore,
client: client,
conversation: conversation,
dispatcher: dispatcher,
sdk: window.OT}
), document.querySelector('#main'));
if (localRoomStore) {
dispatcher.dispatch(
new sharedActions.SetupEmptyRoom({localRoomId: hash[1]}));
return;
}
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
windowId: windowId,
outgoing: outgoing
dispatcher.dispatch(new sharedActions.GetWindowData({
windowId: windowId
}));
}
@ -680,7 +680,7 @@ loop.conversation = (function(mozL10n) {
AppControllerView: AppControllerView,
IncomingConversationView: IncomingConversationView,
IncomingCallView: IncomingCallView,
IncomingCallFailedView: IncomingCallFailedView,
GenericFailureView: GenericFailureView,
init: init
};
})(document.mozL10n);

View File

@ -179,12 +179,12 @@ loop.conversation = (function(mozL10n) {
});
/**
* Incoming Call failed view. Displayed when a call fails.
* Something went wrong view. Displayed when there's a big problem.
*
* XXX Based on CallFailedView, but built specially until we flux-ify the
* incoming call views (bug 1088672).
*/
var IncomingCallFailedView = React.createClass({
var GenericFailureView = React.createClass({
propTypes: {
cancelCall: React.PropTypes.func.isRequired
},
@ -218,7 +218,9 @@ loop.conversation = (function(mozL10n) {
client: React.PropTypes.instanceOf(loop.Client).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
conversationAppStore: React.PropTypes.instanceOf(
loop.store.ConversationAppStore).isRequired
},
getInitialState: function() {
@ -283,7 +285,7 @@ loop.conversation = (function(mozL10n) {
case "end": {
// XXX To be handled with the "failed" view state when bug 1047410 lands
if (this.state.callFailed) {
return <IncomingCallFailedView
return <GenericFailureView
cancelCall={this.closeWindow.bind(this)}
/>
}
@ -352,13 +354,9 @@ loop.conversation = (function(mozL10n) {
setupIncomingCall: function() {
navigator.mozLoop.startAlerting();
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId"));
if (!callData) {
// XXX Not the ideal response, but bug 1047410 will be replacing
// this by better "call failed" UI.
console.error("Failed to get the call data");
return;
}
// XXX This is a hack until we rework for the flux model in bug 1088672.
var callData = this.props.conversationAppStore.getStoreState().windowData;
this.props.conversation.setIncomingSessionData(callData);
this._setupWebSocket();
},
@ -374,7 +372,8 @@ loop.conversation = (function(mozL10n) {
* Moves the call to the end state
*/
endCall: function() {
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
navigator.mozLoop.calls.clearCallInProgress(
this.props.conversation.get("windowId"));
this.setState({callStatus: "end"});
},
@ -475,7 +474,8 @@ loop.conversation = (function(mozL10n) {
*/
_declineCall: function() {
this._websocket.decline();
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
navigator.mozLoop.calls.clearCallInProgress(
this.props.conversation.get("windowId"));
this._websocket.close();
// Having a timeout here lets the logging for the websocket complete and be
// displayed on the console if both are on.
@ -523,6 +523,8 @@ loop.conversation = (function(mozL10n) {
* in progress, and hence, which view to display.
*/
var AppControllerView = React.createClass({
mixins: [Backbone.Events],
propTypes: {
// XXX Old types required for incoming call view.
client: React.PropTypes.instanceOf(loop.Client).isRequired,
@ -530,51 +532,66 @@ loop.conversation = (function(mozL10n) {
.isRequired,
sdk: React.PropTypes.object.isRequired,
// XXX New types for OutgoingConversationView
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
// XXX New types for flux style
conversationAppStore: React.PropTypes.instanceOf(
loop.store.ConversationAppStore).isRequired,
conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore)
.isRequired,
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
// if not passed, this is not a room view
localRoomStore: React.PropTypes.instanceOf(loop.store.LocalRoomStore)
},
getInitialState: function() {
return this.props.store.attributes;
return this.props.conversationAppStore.getStoreState();
},
componentWillMount: function() {
this.props.store.on("change:outgoing", function() {
this.setState(this.props.store.attributes);
this.listenTo(this.props.conversationAppStore, "change", function() {
this.setState(this.props.conversationAppStore.getStoreState());
}, this);
},
componentWillUnmount: function() {
this.stopListening(this.props.conversationAppStore);
},
closeWindow: function() {
window.close();
},
render: function() {
if (this.props.localRoomStore) {
return (
<EmptyRoomView
switch(this.state.windowType) {
case "incoming": {
return (<IncomingConversationView
client={this.props.client}
conversation={this.props.conversation}
sdk={this.props.sdk}
conversationAppStore={this.props.conversationAppStore}
/>);
}
case "outgoing": {
return (<OutgoingConversationView
store={this.props.conversationStore}
dispatcher={this.props.dispatcher}
/>);
}
case "room": {
return (<EmptyRoomView
mozLoop={navigator.mozLoop}
localRoomStore={this.props.localRoomStore}
/>
);
/>);
}
case "failed": {
return (<GenericFailureView
cancelCall={this.closeWindow}
/>);
}
default: {
// If we don't have a windowType, we don't know what we are yet,
// so don't display anything.
return null;
}
}
// Don't display anything, until we know what type of call we are.
if (this.state.outgoing === undefined) {
return null;
}
if (this.state.outgoing) {
return (<OutgoingConversationView
store={this.props.store}
dispatcher={this.props.dispatcher}
/>);
}
return (<IncomingConversationView
client={this.props.client}
conversation={this.props.conversation}
sdk={this.props.sdk}
/>);
}
});
@ -605,11 +622,20 @@ loop.conversation = (function(mozL10n) {
sdk: OT
});
// Create the stores.
var conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
var conversationStore = new loop.store.ConversationStore({}, {
client: client,
dispatcher: dispatcher,
sdkDriver: sdkDriver
});
var localRoomStore = new loop.store.LocalRoomStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});;
// XXX Old class creation for the incoming conversation view, whilst
// we transition across (bug 1072323).
@ -622,57 +648,31 @@ loop.conversation = (function(mozL10n) {
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationData().hash;
var windowId;
var outgoing;
var localRoomStore;
// XXX removeMe, along with noisy comment at the beginning of
// conversation_test.js "when locationHash begins with #room".
if (navigator.mozLoop.getLoopBoolPref("test.alwaysUseRooms")) {
locationHash = "#room/32";
}
var hash = locationHash.match(/#incoming\/(.*)/);
var hash = locationHash.match(/#(.*)/);
if (hash) {
windowId = hash[1];
outgoing = false;
} else if (hash = locationHash.match(/#room\/(.*)/)) {
localRoomStore = new loop.store.LocalRoomStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
} else {
hash = locationHash.match(/#outgoing\/(.*)/);
if (hash) {
windowId = hash[1];
outgoing = true;
}
}
conversation.set({windowId: windowId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(windowId);
navigator.mozLoop.calls.clearCallInProgress(windowId);
});
React.renderComponent(<AppControllerView
conversationAppStore={conversationAppStore}
localRoomStore={localRoomStore}
store={conversationStore}
conversationStore={conversationStore}
client={client}
conversation={conversation}
dispatcher={dispatcher}
sdk={window.OT}
/>, document.querySelector('#main'));
if (localRoomStore) {
dispatcher.dispatch(
new sharedActions.SetupEmptyRoom({localRoomId: hash[1]}));
return;
}
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
windowId: windowId,
outgoing: outgoing
dispatcher.dispatch(new sharedActions.GetWindowData({
windowId: windowId
}));
}
@ -680,7 +680,7 @@ loop.conversation = (function(mozL10n) {
AppControllerView: AppControllerView,
IncomingConversationView: IncomingConversationView,
IncomingCallView: IncomingCallView,
IncomingCallFailedView: IncomingCallFailedView,
GenericFailureView: GenericFailureView,
init: init
};
})(document.mozL10n);

View File

@ -0,0 +1,86 @@
/* 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/. */
/* global loop:true */
var loop = loop || {};
loop.store = loop.store || {};
/**
* Manages the conversation window app controller view. Used to get
* the window data and store the window type.
*/
loop.store.ConversationAppStore = (function() {
/**
* Constructor
*
* @param {Object} options Options for the store. Should contain the dispatcher.
*/
var ConversationAppStore = function(options) {
if (!options.dispatcher) {
throw new Error("Missing option dispatcher");
}
if (!options.mozLoop) {
throw new Error("Missing option mozLoop");
}
this._dispatcher = options.dispatcher;
this._mozLoop = options.mozLoop;
this._storeState = {};
this._dispatcher.register(this, [
"getWindowData"
]);
};
ConversationAppStore.prototype = _.extend({
/**
* Retrieves current store state.
*
* @return {Object}
*/
getStoreState: function() {
return this._storeState;
},
/**
* Updates store states and trigger a "change" event.
*
* @param {Object} state The new store state.
*/
setStoreState: function(state) {
this._storeState = state;
this.trigger("change");
},
/**
* Handles the get window data action - obtains the window data,
* updates the store and notifies interested components.
*
* @param {sharedActions.GetWindowData} actionData The action data
*/
getWindowData: function(actionData) {
var windowData = this._mozLoop.getConversationWindowData(actionData.windowId);
if (!windowData) {
console.error("Failed to get the window data");
this.setStoreState({windowType: "failed"});
return;
}
// XXX windowData is a hack for the IncomingConversationView until
// we rework it for the flux model in bug 1088672.
this.setStoreState({
windowType: windowData.type,
windowData: windowData
});
this._dispatcher.dispatch(new loop.shared.actions.SetupWindowData(_.extend({
windowId: actionData.windowId}, windowData)));
}
}, Backbone.Events);
return ConversationAppStore;
})();

View File

@ -499,6 +499,10 @@ loop.conversationViews = (function(mozL10n) {
case CALL_STATES.FINISHED: {
return this._renderFeedbackView();
}
case CALL_STATES.INIT: {
// We know what we are, but we haven't got the data yet.
return null;
}
default: {
return (PendingConversationView({
dispatcher: this.props.dispatcher,

View File

@ -499,6 +499,10 @@ loop.conversationViews = (function(mozL10n) {
case CALL_STATES.FINISHED: {
return this._renderFeedbackView();
}
case CALL_STATES.INIT: {
// We know what we are, but we haven't got the data yet.
return null;
}
default: {
return (<PendingConversationView
dispatcher={this.props.dispatcher}

View File

@ -582,7 +582,9 @@ loop.panel = (function(_, mozL10n) {
},
openRoom: function(room) {
// XXX implement me; see bug 1074678
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: room.roomToken
}));
},
render: function() {

View File

@ -582,7 +582,9 @@ loop.panel = (function(_, mozL10n) {
},
openRoom: function(room) {
// XXX implement me; see bug 1074678
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: room.roomToken
}));
},
render: function() {

View File

@ -30,6 +30,27 @@ loop.shared.actions = (function() {
};
return {
/**
* Get the window data for the provided window id
*/
GetWindowData: Action.define("getWindowData", {
windowId: String
}),
/**
* Used to pass round the window data so that stores can
* record the appropriate data.
*/
SetupWindowData: Action.define("setupWindowData", {
windowId: String,
type: String
// Optional Items. There are other optional items typically sent
// around with this action. They are for the setup of calls and rooms and
// depend on the type. See LoopCalls and LoopRooms for the details of this
// data.
}),
/**
* Fetch a new call url from the server, intended to be sent over email when
* a contact can't be reached.
@ -37,15 +58,6 @@ loop.shared.actions = (function() {
FetchEmailLink: Action.define("fetchEmailLink", {
}),
/**
* Used to trigger gathering of initial call data.
*/
GatherCallData: Action.define("gatherCallData", {
// Specify the callId for an incoming call.
windowId: [String, null],
outgoing: Boolean
}),
/**
* Used to cancel call setup.
*/
@ -170,13 +182,11 @@ loop.shared.actions = (function() {
}),
/**
* Primes localRoomStore with roomLocalId, which triggers the EmptyRoomView
* to do any necessary setup.
*
* XXX should move to localRoomActions module
* Opens a room.
* XXX: should move to some roomActions module - refs bug 1079284
*/
SetupEmptyRoom: Action.define("setupEmptyRoom", {
localRoomId: String
OpenRoom: Action.define("openRoom", {
roomToken: String
})
};
})();

View File

@ -121,7 +121,7 @@ loop.store.ConversationStore = (function() {
this.dispatcher.register(this, [
"connectionFailure",
"connectionProgress",
"gatherCallData",
"setupWindowData",
"connectCall",
"hangupCall",
"peerHungupCall",
@ -188,37 +188,23 @@ loop.store.ConversationStore = (function() {
}
},
/**
* Handles the gather call data action, setting the state
* and starting to get the appropriate data for the type of call.
*
* @param {sharedActions.GatherCallData} actionData The action data.
*/
gatherCallData: function(actionData) {
if (!actionData.outgoing) {
// XXX Other types aren't supported yet, but set the state for the
// view selection.
this.set({outgoing: false});
return;
}
var callData = navigator.mozLoop.getCallData(actionData.windowId);
if (!callData) {
console.error("Failed to get the call data");
this.set({callState: CALL_STATES.TERMINATED});
setupWindowData: function(actionData) {
var windowType = actionData.type;
if (windowType !== "outgoing" &&
windowType !== "incoming") {
// Not for this store, don't do anything.
return;
}
this.set({
contact: callData.contact,
outgoing: actionData.outgoing,
contact: actionData.contact,
outgoing: windowType === "outgoing",
windowId: actionData.windowId,
callType: callData.callType,
callState: CALL_STATES.GATHER
callType: actionData.callType,
callState: CALL_STATES.GATHER,
videoMuted: actionData.callType === CALL_TYPES.AUDIO_ONLY
});
this.set({videoMuted: this.get("callType") === CALL_TYPES.AUDIO_ONLY});
if (this.get("outgoing")) {
this._setupOutgoingCall();
} // XXX Else, other types aren't supported yet.
@ -330,6 +316,8 @@ loop.store.ConversationStore = (function() {
var contactAddresses = [];
var contact = this.get("contact");
navigator.mozLoop.calls.setCallInProgress(this.get("windowId"));
function appendContactValues(property, strip) {
if (contact.hasOwnProperty(property)) {
contact[property].forEach(function(item) {
@ -409,7 +397,7 @@ loop.store.ConversationStore = (function() {
delete this._websocket;
}
navigator.mozLoop.releaseCallData(this.get("windowId"));
navigator.mozLoop.calls.clearCallInProgress(this.get("windowId"));
},
/**

View File

@ -36,7 +36,9 @@ loop.store.LocalRoomStore = (function() {
}
this.mozLoop = options.mozLoop;
this.dispatcher.register(this, ["setupEmptyRoom"]);
this.dispatcher.register(this, [
"setupWindowData"
]);
}
LocalRoomStore.prototype = _.extend({
@ -69,24 +71,8 @@ loop.store.LocalRoomStore = (function() {
},
/**
* Proxy to mozLoop.rooms.getRoomData for setupEmptyRoom action.
*
* XXXremoveMe Can probably be removed when bug 1074664 lands.
*
* @param {sharedActions.setupEmptyRoom} actionData
* @param {Function} cb Callback(error, roomData)
*/
_fetchRoomData: function(actionData, cb) {
if (this.mozLoop.rooms && this.mozLoop.rooms.getRoomData) {
this.mozLoop.rooms.getRoomData(actionData.localRoomId, cb);
} else {
cb(null, {roomName: "Donkeys"});
}
},
/**
* Execute setupEmptyRoom event action from the dispatcher. This primes
* the store with the localRoomId, and calls MozLoop.getRoomData on that
* 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.
@ -94,16 +80,22 @@ loop.store.LocalRoomStore = (function() {
* When the room name gets set, that will trigger the view to display
* that name.
*
* @param {sharedActions.setupEmptyRoom} actionData
* @param {sharedActions.SetupWindowData} actionData
*/
setupEmptyRoom: function(actionData) {
this._fetchRoomData(actionData, function(error, roomData) {
this.setStoreState({
error: error,
localRoomId: actionData.localRoomId,
serverData: roomData
});
}.bind(this));
setupWindowData: function(actionData) {
if (actionData.type !== "room") {
// Nothing for us to do here, leave it to other stores.
return;
}
this.mozLoop.rooms.get(actionData.roomToken,
function(error, roomData) {
this.setStoreState({
error: error,
roomToken: actionData.roomToken,
serverData: roomData
});
}.bind(this));
}
}, Backbone.Events);

View File

@ -328,6 +328,15 @@ loop.store = loop.store || {};
rooms: this._processRoomList(actionData.roomList)
});
},
/**
* Opens a room
*
* @param {sharedActions.OpenRoom} actionData The action data.
*/
openRoom: function(actionData) {
this._mozLoop.rooms.open(actionData.roomToken);
}
}, Backbone.Events);
loop.store.RoomListStore = RoomListStore;

View File

@ -13,6 +13,7 @@ browser.jar:
# Desktop script
content/browser/loop/js/client.js (content/js/client.js)
content/browser/loop/js/conversation.js (content/js/conversation.js)
content/browser/loop/js/conversationAppStore.js (content/js/conversationAppStore.js)
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
content/browser/loop/js/panel.js (content/js/panel.js)
content/browser/loop/js/contacts.js (content/js/contacts.js)

View File

@ -0,0 +1,85 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var expect = chai.expect;
describe("loop.store.ConversationAppStore", function () {
var sharedActions = loop.shared.actions;
var sandbox, dispatcher;
beforeEach(function() {
sandbox = sinon.sandbox.create();
dispatcher = new loop.Dispatcher();
});
afterEach(function() {
sandbox.restore();
});
describe("#constructor", function() {
it("should throw an error if the dispatcher is missing", function() {
expect(function() {
new loop.store.ConversationAppStore({mozLoop: {}});
}).to.Throw(/dispatcher/);
});
it("should throw an error if mozLoop is missing", function() {
expect(function() {
new loop.store.ConversationAppStore({dispatcher: dispatcher});
}).to.Throw(/mozLoop/);
});
});
describe("#getWindowData", function() {
var fakeWindowData, fakeGetWindowData, fakeMozLoop, store;
beforeEach(function() {
fakeWindowData = {
type: "incoming",
callId: "123456"
};
fakeGetWindowData = {
windowId: "42"
};
fakeMozLoop = {
getConversationWindowData: function(windowId) {
if (windowId === "42") {
return fakeWindowData;
}
return null;
}
};
store = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
mozLoop: fakeMozLoop
});
});
it("should fetch the window type from the mozLoop API", function() {
dispatcher.dispatch(new sharedActions.GetWindowData(fakeGetWindowData));
expect(store.getStoreState()).eql({
windowType: "incoming",
windowData: fakeWindowData
});
});
it("should dispatch a SetupWindowData action with the data from the mozLoop API",
function() {
sandbox.stub(dispatcher, "dispatch");
store.getWindowData(new sharedActions.GetWindowData(fakeGetWindowData));
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.SetupWindowData(_.extend({
windowId: fakeGetWindowData.windowId
}, fakeWindowData)));
});
});
});

View File

@ -438,10 +438,10 @@ describe("loop.conversationViews", function () {
loop.conversationViews.CallFailedView);
});
it("should render the PendingConversationView when the call state is 'init'",
it("should render the PendingConversationView when the call state is 'gather'",
function() {
store.set({
callState: CALL_STATES.INIT,
callState: CALL_STATES.GATHER,
contact: contact
});
@ -474,7 +474,7 @@ describe("loop.conversationViews", function () {
it("should update the rendered views when the state is changed.",
function() {
store.set({
callState: CALL_STATES.INIT,
callState: CALL_STATES.GATHER,
contact: contact
});

View File

@ -41,8 +41,9 @@ describe("loop.conversation", function() {
setLoopCharPref: sinon.stub(),
getLoopCharPref: sinon.stub().returns("http://fakeurl"),
getLoopBoolPref: sinon.stub(),
getCallData: sinon.stub(),
releaseCallData: sinon.stub(),
calls: {
clearCallInProgress: sinon.stub()
},
startAlerting: sinon.stub(),
stopAlerting: sinon.stub(),
ensureRegistered: sinon.stub(),
@ -80,7 +81,7 @@ describe("loop.conversation", function() {
sandbox.stub(loop.shared.utils.Helper.prototype,
"locationData").returns({
hash: "#incoming/42",
hash: "#42",
pathname: "/"
});
@ -112,86 +113,30 @@ describe("loop.conversation", function() {
}));
});
describe("when locationHash begins with #room", function () {
// XXX must stay in sync with "test.alwaysUseRooms" pref check
// in conversation.jsx:init until we remove that code, which should
// happen in the second patch in bug 1074686, at which time this comment
// can go away as well.
var fakeRoomID = "32";
beforeEach(function() {
loop.shared.utils.Helper.prototype.locationData
.returns({
hash: "#room/" + fakeRoomID,
pathname: ""
});
sandbox.stub(loop.store, "LocalRoomStore");
});
it("should create a localRoomStore", function() {
loop.conversation.init();
sinon.assert.calledOnce(loop.store.LocalRoomStore);
sinon.assert.calledWithNew(loop.store.LocalRoomStore);
sinon.assert.calledWithExactly(loop.store.LocalRoomStore,
sinon.match({
dispatcher: sinon.match.instanceOf(loop.Dispatcher),
mozLoop: sinon.match.same(navigator.mozLoop)
}));
});
it("should dispatch SetupEmptyRoom with localRoomId from locationHash",
function() {
loop.conversation.init();
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.SetupEmptyRoom({localRoomId: fakeRoomID}));
});
});
it("should trigger a gatherCallData action", function() {
it("should trigger a getWindowData action", function() {
loop.conversation.init();
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
windowId: "42",
outgoing: false
new loop.shared.actions.GetWindowData({
windowId: "42"
}));
});
it("should trigger an outgoing gatherCallData action for outgoing calls",
function() {
loop.shared.utils.Helper.prototype.locationData.returns({
hash: "#outgoing/24",
pathname: "/"
});
loop.conversation.init();
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
windowId: "24",
outgoing: true
}));
});
});
describe("ConversationControllerView", function() {
var store, conversation, client, ccView, oldTitle, dispatcher;
describe("AppControllerView", function() {
var conversationStore, conversation, client, ccView, oldTitle, dispatcher;
var conversationAppStore, localRoomStore;
function mountTestComponent(localRoomStore) {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
loop.conversation.AppControllerView({
client: client,
conversation: conversation,
localRoomStore: localRoomStore,
sdk: {},
store: store
conversationStore: conversationStore,
conversationAppStore: conversationAppStore
}));
}
@ -202,7 +147,7 @@ describe("loop.conversation", function() {
sdk: {}
});
dispatcher = new loop.Dispatcher();
store = new loop.store.ConversationStore({
conversationStore = new loop.store.ConversationStore({
contact: {
name: [ "Mr Smith" ],
email: [{
@ -216,6 +161,14 @@ describe("loop.conversation", function() {
dispatcher: dispatcher,
sdkDriver: {}
});
localRoomStore = new loop.store.LocalRoomStore({
mozLoop: navigator.mozLoop,
dispatcher: dispatcher
});
conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
});
afterEach(function() {
@ -224,7 +177,7 @@ describe("loop.conversation", function() {
});
it("should display the OutgoingConversationView for outgoing calls", function() {
store.set({outgoing: true});
conversationAppStore.setStoreState({windowType: "outgoing"});
ccView = mountTestComponent();
@ -233,7 +186,14 @@ describe("loop.conversation", function() {
});
it("should display the IncomingConversationView for incoming calls", function() {
store.set({outgoing: false});
sandbox.stub(conversation, "setIncomingSessionData");
sandbox.stub(loop, "CallConnectionWebSocket").returns({
promiseConnect: function() {
return new Promise(function() {});
},
on: sandbox.spy()
});
conversationAppStore.setStoreState({windowType: "incoming"});
ccView = mountTestComponent();
@ -242,31 +202,34 @@ describe("loop.conversation", function() {
});
it("should display the EmptyRoomView for rooms", function() {
navigator.mozLoop.rooms = {
addCallback: function() {},
removeCallback: function() {}
};
var localRoomStore = new loop.store.LocalRoomStore({
mozLoop: navigator.mozLoop,
dispatcher: dispatcher
});
conversationAppStore.setStoreState({windowType: "room"});
ccView = mountTestComponent(localRoomStore);
ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.EmptyRoomView);
});
it("should display the GenericFailureView for failures", function() {
conversationAppStore.setStoreState({windowType: "failed"});
ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView,
loop.conversation.GenericFailureView);
});
});
describe("IncomingConversationView", function() {
var conversation, client, icView, oldTitle;
var conversationAppStore, conversation, client, icView, oldTitle;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
loop.conversation.IncomingConversationView({
client: client,
conversation: conversation,
sdk: {}
sdk: {},
conversationAppStore: conversationAppStore
}));
}
@ -277,6 +240,11 @@ describe("loop.conversation", function() {
sdk: {}
});
conversation.set({windowId: 42});
var dispatcher = new loop.Dispatcher();
conversationAppStore = new loop.store.ConversationAppStore({
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
sandbox.stub(conversation, "setOutgoingSessionData");
});
@ -287,13 +255,13 @@ describe("loop.conversation", function() {
describe("start", function() {
it("should set the title to incoming_call_title2", function() {
navigator.mozLoop.getCallData = function() {
return {
conversationAppStore.setStoreState({
windowData: {
progressURL: "fake",
websocketToken: "fake",
callId: 42
};
};
}
});
icView = mountTestComponent();
@ -302,7 +270,8 @@ describe("loop.conversation", function() {
});
describe("componentDidMount", function() {
var fakeSessionData;
var fakeSessionData, promise, resolveWebSocketConnect;
var rejectWebSocketConnect;
beforeEach(function() {
fakeSessionData = {
@ -315,7 +284,10 @@ describe("loop.conversation", function() {
websocketToken: "7b"
};
navigator.mozLoop.getCallData.returns(fakeSessionData);
conversationAppStore.setStoreState({
windowData: fakeSessionData
});
stubComponent(loop.conversation, "IncomingCallView");
stubComponent(sharedView, "ConversationView");
});
@ -326,179 +298,167 @@ describe("loop.conversation", function() {
sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
});
it("should call getCallData on navigator.mozLoop", function() {
icView = mountTestComponent();
sinon.assert.calledOnce(navigator.mozLoop.getCallData);
sinon.assert.calledWith(navigator.mozLoop.getCallData, 42);
});
describe("getCallData successful", function() {
var promise, resolveWebSocketConnect,
rejectWebSocketConnect;
describe("Session Data setup", function() {
beforeEach(function() {
sandbox.stub(loop, "CallConnectionWebSocket").returns({
promiseConnect: function () {
promise = new Promise(function(resolve, reject) {
resolveWebSocketConnect = resolve;
rejectWebSocketConnect = reject;
});
return promise;
},
on: sinon.stub()
});
});
it("should store the session data", function() {
sandbox.stub(conversation, "setIncomingSessionData");
icView = mountTestComponent();
sinon.assert.calledOnce(conversation.setIncomingSessionData);
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
fakeSessionData);
});
it("should setup the websocket connection", function() {
icView = mountTestComponent();
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
callId: "Hello",
url: "http://progress.example.com",
websocketToken: "7b"
});
describe("Session Data setup", function() {
beforeEach(function() {
sandbox.stub(loop, "CallConnectionWebSocket").returns({
promiseConnect: function () {
promise = new Promise(function(resolve, reject) {
resolveWebSocketConnect = resolve;
rejectWebSocketConnect = reject;
});
return promise;
},
on: sinon.stub()
});
});
describe("WebSocket Handling", function() {
beforeEach(function() {
promise = new Promise(function(resolve, reject) {
resolveWebSocketConnect = resolve;
rejectWebSocketConnect = reject;
});
it("should store the session data", function() {
sandbox.stub(conversation, "setIncomingSessionData");
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
icView = mountTestComponent();
sinon.assert.calledOnce(conversation.setIncomingSessionData);
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
fakeSessionData);
});
it("should setup the websocket connection", function() {
icView = mountTestComponent();
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
callId: "Hello",
url: "http://progress.example.com",
websocketToken: "7b"
});
});
});
describe("WebSocket Handling", function() {
beforeEach(function() {
promise = new Promise(function(resolve, reject) {
resolveWebSocketConnect = resolve;
rejectWebSocketConnect = reject;
});
it("should set the state to incoming on success", function(done) {
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
});
it("should set the state to incoming on success", function(done) {
icView = mountTestComponent();
resolveWebSocketConnect("incoming");
promise.then(function () {
expect(icView.state.callStatus).eql("incoming");
done();
});
});
it("should set the state to close on success if the progress " +
"state is terminated", function(done) {
icView = mountTestComponent();
resolveWebSocketConnect("incoming");
resolveWebSocketConnect("terminated");
promise.then(function () {
expect(icView.state.callStatus).eql("incoming");
expect(icView.state.callStatus).eql("close");
done();
});
});
it("should set the state to close on success if the progress " +
"state is terminated", function(done) {
icView = mountTestComponent();
resolveWebSocketConnect("terminated");
// XXX implement me as part of bug 1047410
// see https://hg.mozilla.org/integration/fx-team/rev/5d2c69ebb321#l18.259
it.skip("should should switch view state to failed", function(done) {
icView = mountTestComponent();
rejectWebSocketConnect();
promise.then(function () {
expect(icView.state.callStatus).eql("close");
promise.then(function() {}, function() {
done();
});
});
});
describe("WebSocket Events", function() {
describe("Call cancelled or timed out before acceptance", function() {
beforeEach(function() {
// Mounting the test component automatically calls the required
// setup functions
icView = mountTestComponent();
promise = new Promise(function(resolve, reject) {
resolve();
});
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
sandbox.stub(window, "close");
});
describe("progress - terminated (previousState = alerting)", function() {
it("should stop alerting", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "timeout"
}, "alerting");
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
done();
});
});
// XXX implement me as part of bug 1047410
// see https://hg.mozilla.org/integration/fx-team/rev/5d2c69ebb321#l18.259
it.skip("should should switch view state to failed", function(done) {
icView = mountTestComponent();
rejectWebSocketConnect();
it("should close the websocket", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "closed"
}, "alerting");
promise.then(function() {}, function() {
done();
sinon.assert.calledOnce(icView._websocket.close);
done();
});
});
it("should close the window", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "answered-elsewhere"
}, "alerting");
sandbox.clock.tick(1);
sinon.assert.calledOnce(window.close);
done();
});
});
});
});
describe("WebSocket Events", function() {
describe("Call cancelled or timed out before acceptance", function() {
beforeEach(function() {
// Mounting the test component automatically calls the required
// setup functions
icView = mountTestComponent();
promise = new Promise(function(resolve, reject) {
resolve();
describe("progress - terminated (previousState not init" +
" nor alerting)",
function() {
it("should set the state to end", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "media-fail"
}, "connecting");
expect(icView.state.callStatus).eql("end");
done();
});
});
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
sandbox.stub(window, "close");
});
describe("progress - terminated (previousState = alerting)", function() {
it("should stop alerting", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "timeout"
}, "alerting");
reason: "media-fail"
}, "connecting");
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
done();
});
});
it("should close the websocket", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "closed"
}, "alerting");
sinon.assert.calledOnce(icView._websocket.close);
done();
});
});
it("should close the window", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "answered-elsewhere"
}, "alerting");
sandbox.clock.tick(1);
sinon.assert.calledOnce(window.close);
done();
});
});
});
describe("progress - terminated (previousState not init" +
" nor alerting)",
function() {
it("should set the state to end", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "media-fail"
}, "connecting");
expect(icView.state.callStatus).eql("end");
done();
});
});
it("should stop alerting", function(done) {
promise.then(function() {
icView._websocket.trigger("progress", {
state: "terminated",
reason: "media-fail"
}, "connecting");
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
done();
});
});
});
});
});
});
@ -572,8 +532,9 @@ describe("loop.conversation", function() {
it("should release callData", function() {
icView.decline();
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "8699");
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "8699");
});
});
@ -644,13 +605,27 @@ describe("loop.conversation", function() {
var fakeSessionData;
beforeEach(function() {
icView = mountTestComponent();
fakeSessionData = {
sessionId: "sessionId",
sessionToken: "sessionToken",
apiKey: "apiKey"
};
conversationAppStore.setStoreState({
windowData: fakeSessionData
});
sandbox.stub(conversation, "setIncomingSessionData");
sandbox.stub(loop, "CallConnectionWebSocket").returns({
promiseConnect: function() {
return new Promise(function() {});
},
on: sandbox.spy()
});
icView = mountTestComponent();
conversation.set("loopToken", "fakeToken");
navigator.mozLoop.getLoopCharPref.returns("http://fake");
stubComponent(sharedView, "ConversationView");
@ -700,7 +675,7 @@ describe("loop.conversation", function() {
conversation.trigger("session:network-disconnected");
TestUtils.findRenderedComponentWithType(icView,
loop.conversation.IncomingCallFailedView);
loop.conversation.GenericFailureView);
});
it("should update the conversation window toolbar title",

View File

@ -46,6 +46,7 @@
<script src="../../content/shared/js/roomListStore.js"></script>
<script src="../../content/js/client.js"></script>
<script src="../../content/shared/js/localRoomStore.js"></script>
<script src="../../content/js/conversationAppStore.js"></script>
<script src="../../content/js/roomViews.js"></script>
<script src="../../content/js/conversationViews.js"></script>
<script src="../../content/js/conversation.js"></script>
@ -53,6 +54,7 @@
<script src="../../content/js/panel.js"></script>
<!-- Test scripts -->
<script src="conversationAppStore_test.js"></script>
<script src="client_test.js"></script>
<script src="conversation_test.js"></script>
<script src="panel_test.js"></script>

View File

@ -763,7 +763,7 @@ describe("loop.panel", function() {
var buttonNode = view.getDOMNode().querySelector("button[disabled]");
expect(buttonNode).to.not.equal(null);
});
});
it("should disable the create button when a list retrieval operation is pending",
function() {
@ -774,6 +774,20 @@ describe("loop.panel", function() {
var buttonNode = view.getDOMNode().querySelector("button[disabled]");
expect(buttonNode).to.not.equal(null);
});
describe("#openRoom", function() {
it("should dispatch an OpenRoom action", function() {
var view = createTestComponent();
var dispatch = sandbox.stub(dispatcher, "dispatch");
view.openRoom({roomToken: "42cba"});
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWithExactly(dispatch, new sharedActions.OpenRoom({
roomToken: "42cba"
}));
});
});
});

View File

@ -79,25 +79,23 @@ const kDanglingContact = {
};
const promiseLoadContacts = function() {
let deferred = Promise.defer();
LoopContacts.removeAll(err => {
if (err) {
deferred.reject(err);
return;
}
gExpectedAdds.push(...kContacts);
LoopContacts.addMany(kContacts, (err, contacts) => {
return new Promise((resolve, reject) => {
LoopContacts.removeAll(err => {
if (err) {
deferred.reject(err);
reject(err);
return;
}
deferred.resolve(contacts);
gExpectedAdds.push(...kContacts);
LoopContacts.addMany(kContacts, (err, contacts) => {
if (err) {
reject(err);
return;
}
resolve(contacts);
});
});
});
return deferred.promise;
};
// Get a copy of a contact without private properties.
@ -162,36 +160,36 @@ add_task(function* () {
}
info("Add a contact.");
let deferred = Promise.defer();
gExpectedAdds.push(kDanglingContact);
LoopContacts.add(kDanglingContact, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kDanglingContact);
info("Check if it's persisted.");
LoopContacts.get(contact._guid, (err, contact) => {
yield new Promise((resolve, reject) => {
gExpectedAdds.push(kDanglingContact);
LoopContacts.add(kDanglingContact, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kDanglingContact);
deferred.resolve();
info("Check if it's persisted.");
LoopContacts.get(contact._guid, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kDanglingContact);
resolve();
});
});
});
yield deferred.promise;
});
add_task(function* () {
info("Test removing all contacts.");
let contacts = yield promiseLoadContacts();
let deferred = Promise.defer();
LoopContacts.removeAll(function(err) {
Assert.ok(!err, "There shouldn't be an error");
LoopContacts.getAll(function(err, found) {
yield new Promise((resolve, reject) => {
LoopContacts.removeAll(function(err) {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(found.length, 0, "There shouldn't be any contacts left");
deferred.resolve();
})
LoopContacts.getAll(function(err, found) {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(found.length, 0, "There shouldn't be any contacts left");
resolve();
})
});
});
yield deferred.promise;
});
// Test retrieving a contact.
@ -199,58 +197,58 @@ add_task(function* () {
let contacts = yield promiseLoadContacts();
info("Get a single contact.");
let deferred = Promise.defer();
LoopContacts.get(contacts[1]._guid, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kContacts[1]);
deferred.resolve();
yield new Promise((resolve, reject) => {
LoopContacts.get(contacts[1]._guid, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kContacts[1]);
resolve();
});
});
yield deferred.promise;
info("Get a single contact by id.");
deferred = Promise.defer();
LoopContacts.getByServiceId(2, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kContacts[1]);
deferred.resolve();
yield new Promise((resolve, reject) => {
LoopContacts.getByServiceId(2, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
compareContacts(contact, kContacts[1]);
resolve();
});
});
yield deferred.promise;
info("Get a couple of contacts.");
deferred = Promise.defer();
let toRetrieve = [contacts[0], contacts[2], contacts[3]];
LoopContacts.getMany(toRetrieve.map(contact => contact._guid), (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result.length, toRetrieve.length, "Result list should be the same " +
"size as the list of items to retrieve");
for (let contact of toRetrieve) {
let found = result.filter(c => c._guid == contact._guid);
Assert.ok(found.length, "Contact " + contact._guid + " should be in the list");
compareContacts(found[0], contact);
}
deferred.resolve();
yield new Promise((resolve, reject) => {
let toRetrieve = [contacts[0], contacts[2], contacts[3]];
LoopContacts.getMany(toRetrieve.map(contact => contact._guid), (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result.length, toRetrieve.length, "Result list should be the same " +
"size as the list of items to retrieve");
for (let contact of toRetrieve) {
let found = result.filter(c => c._guid == contact._guid);
Assert.ok(found.length, "Contact " + contact._guid + " should be in the list");
compareContacts(found[0], contact);
}
resolve();
});
});
yield deferred.promise;
info("Get all contacts.");
deferred = Promise.defer();
LoopContacts.getAll((err, contacts) => {
Assert.ok(!err, "There shouldn't be an error");
for (let i = 0, l = contacts.length; i < l; ++i) {
compareContacts(contacts[i], kContacts[i]);
}
deferred.resolve();
yield new Promise((resolve, reject) => {
LoopContacts.getAll((err, contacts) => {
Assert.ok(!err, "There shouldn't be an error");
for (let i = 0, l = contacts.length; i < l; ++i) {
compareContacts(contacts[i], kContacts[i]);
}
resolve();
});
});
yield deferred.promise;
info("Get a non-existent contact.");
deferred = Promise.defer();
LoopContacts.get(1000, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.ok(!contact, "There shouldn't be a contact");
deferred.resolve();
return new Promise((resolve, reject) => {
LoopContacts.get(1000, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.ok(!contact, "There shouldn't be a contact");
resolve();
});
});
yield deferred.promise;
});
// Test removing a contact.
@ -258,47 +256,47 @@ add_task(function* () {
let contacts = yield promiseLoadContacts();
info("Remove a single contact.");
let deferred = Promise.defer();
let toRemove = contacts[2]._guid;
gExpectedRemovals.push(toRemove);
LoopContacts.remove(toRemove, err => {
Assert.ok(!err, "There shouldn't be an error");
LoopContacts.get(toRemove, (err, contact) => {
yield new Promise((resolve, reject) => {
let toRemove = contacts[2]._guid;
gExpectedRemovals.push(toRemove);
LoopContacts.remove(toRemove, err => {
Assert.ok(!err, "There shouldn't be an error");
Assert.ok(!contact, "There shouldn't be a contact");
deferred.resolve();
LoopContacts.get(toRemove, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.ok(!contact, "There shouldn't be a contact");
resolve();
});
});
});
yield deferred.promise;
info("Remove a non-existing contact.");
deferred = Promise.defer();
LoopContacts.remove(1000, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.ok(!contact, "There shouldn't be a contact");
deferred.resolve();
});
yield deferred.promise;
info("Remove multiple contacts.");
deferred = Promise.defer();
toRemove = [contacts[0]._guid, contacts[1]._guid];
gExpectedRemovals.push(...toRemove);
LoopContacts.removeMany(toRemove, err => {
Assert.ok(!err, "There shouldn't be an error");
LoopContacts.getAll((err, contacts) => {
yield new Promise((resolve, reject) => {
LoopContacts.remove(1000, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
let ids = contacts.map(contact => contact._guid);
Assert.equal(ids.indexOf(toRemove[0]), -1, "Contact '" + toRemove[0] +
"' shouldn't be there");
Assert.equal(ids.indexOf(toRemove[1]), -1, "Contact '" + toRemove[1] +
"' shouldn't be there");
deferred.resolve();
Assert.ok(!contact, "There shouldn't be a contact");
resolve();
});
});
info("Remove multiple contacts.");
yield new Promise((resolve, reject) => {
let toRemove = [contacts[0]._guid, contacts[1]._guid];
gExpectedRemovals.push(...toRemove);
LoopContacts.removeMany(toRemove, err => {
Assert.ok(!err, "There shouldn't be an error");
LoopContacts.getAll((err, contacts) => {
Assert.ok(!err, "There shouldn't be an error");
let ids = contacts.map(contact => contact._guid);
Assert.equal(ids.indexOf(toRemove[0]), -1, "Contact '" + toRemove[0] +
"' shouldn't be there");
Assert.equal(ids.indexOf(toRemove[1]), -1, "Contact '" + toRemove[1] +
"' shouldn't be there");
resolve();
});
});
});
yield deferred.promise;
});
// Test updating a contact.
@ -308,40 +306,40 @@ add_task(function* () {
const newBday = (new Date(403920000000)).toISOString();
info("Update a single contact.");
let deferred = Promise.defer();
let toUpdate = {
_guid: contacts[2]._guid,
bday: newBday
};
gExpectedUpdates.push(contacts[2]._guid);
LoopContacts.update(toUpdate, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result, toUpdate._guid, "Result should be the same as the contact ID");
LoopContacts.get(toUpdate._guid, (err, contact) => {
yield new Promise((resolve, reject) => {
let toUpdate = {
_guid: contacts[2]._guid,
bday: newBday
};
gExpectedUpdates.push(contacts[2]._guid);
LoopContacts.update(toUpdate, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(contact.bday, newBday, "Birthday should be the same");
info("Check that all other properties were left intact.");
contacts[2].bday = newBday;
compareContacts(contact, contacts[2]);
deferred.resolve();
Assert.equal(result, toUpdate._guid, "Result should be the same as the contact ID");
LoopContacts.get(toUpdate._guid, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(contact.bday, newBday, "Birthday should be the same");
info("Check that all other properties were left intact.");
contacts[2].bday = newBday;
compareContacts(contact, contacts[2]);
resolve();
});
});
});
yield deferred.promise;
info("Update a non-existing contact.");
deferred = Promise.defer();
toUpdate = {
_guid: 1000,
bday: newBday
};
LoopContacts.update(toUpdate, (err, contact) => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
deferred.resolve();
yield new Promise((resolve, reject) => {
let toUpdate = {
_guid: 1000,
bday: newBday
};
LoopContacts.update(toUpdate, (err, contact) => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
resolve();
});
});
yield deferred.promise;
});
// Test blocking and unblocking a contact.
@ -349,62 +347,62 @@ add_task(function* () {
let contacts = yield promiseLoadContacts();
info("Block contact.");
let deferred = Promise.defer();
let toBlock = contacts[1]._guid;
gExpectedUpdates.push(toBlock);
LoopContacts.block(toBlock, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result, toBlock, "Result should be the same as the contact ID");
LoopContacts.get(toBlock, (err, contact) => {
yield new Promise((resolve, reject) => {
let toBlock = contacts[1]._guid;
gExpectedUpdates.push(toBlock);
LoopContacts.block(toBlock, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.strictEqual(contact.blocked, true, "Blocked status should be set");
info("Check that all other properties were left intact.");
delete contact.blocked;
compareContacts(contact, contacts[1]);
deferred.resolve();
Assert.equal(result, toBlock, "Result should be the same as the contact ID");
LoopContacts.get(toBlock, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.strictEqual(contact.blocked, true, "Blocked status should be set");
info("Check that all other properties were left intact.");
delete contact.blocked;
compareContacts(contact, contacts[1]);
resolve();
});
});
});
yield deferred.promise;
info("Block a non-existing contact.");
deferred = Promise.defer();
LoopContacts.block(1000, err => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
deferred.resolve();
});
yield deferred.promise;
info("Unblock a contact.");
deferred = Promise.defer();
let toUnblock = contacts[1]._guid;
gExpectedUpdates.push(toUnblock);
LoopContacts.unblock(toUnblock, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result, toUnblock, "Result should be the same as the contact ID");
LoopContacts.get(toUnblock, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.strictEqual(contact.blocked, false, "Blocked status should be set");
info("Check that all other properties were left intact.");
delete contact.blocked;
compareContacts(contact, contacts[1]);
deferred.resolve();
yield new Promise((resolve, reject) => {
LoopContacts.block(1000, err => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
resolve();
});
});
info("Unblock a contact.");
yield new Promise((resolve, reject) => {
let toUnblock = contacts[1]._guid;
gExpectedUpdates.push(toUnblock);
LoopContacts.unblock(toUnblock, (err, result) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.equal(result, toUnblock, "Result should be the same as the contact ID");
LoopContacts.get(toUnblock, (err, contact) => {
Assert.ok(!err, "There shouldn't be an error");
Assert.strictEqual(contact.blocked, false, "Blocked status should be set");
info("Check that all other properties were left intact.");
delete contact.blocked;
compareContacts(contact, contacts[1]);
resolve();
});
});
});
yield deferred.promise;
info("Unblock a non-existing contact.");
deferred = Promise.defer();
LoopContacts.unblock(1000, err => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
deferred.resolve();
yield new Promise((resolve, reject) => {
LoopContacts.unblock(1000, err => {
Assert.ok(err, "There should be an error");
Assert.equal(err.message, "Contact with _guid '1000' could not be found",
"Error message should be correct");
resolve();
});
});
yield deferred.promise;
});
// Test if the event emitter implementation doesn't leak and is working as expected.

View File

@ -418,21 +418,21 @@ add_task(function* openFxASettings() {
};
yield promiseOAuthParamsSetup(BASE_URL, params);
let deferredTab = Promise.defer();
let progressListener = {
onLocationChange: function onLocationChange(aBrowser) {
gBrowser.removeTabsProgressListener(progressListener);
let contentURI = Services.io.newURI(params.content_uri, null, null);
is(aBrowser.currentURI.spec, Services.io.newURI("/settings", null, contentURI).spec,
"Check settings tab URL");
deferredTab.resolve();
},
};
gBrowser.addTabsProgressListener(progressListener);
yield new Promise((resolve, reject) => {
let progressListener = {
onLocationChange: function onLocationChange(aBrowser) {
gBrowser.removeTabsProgressListener(progressListener);
let contentURI = Services.io.newURI(params.content_uri, null, null);
is(aBrowser.currentURI.spec, Services.io.newURI("/settings", null, contentURI).spec,
"Check settings tab URL");
resolve();
},
};
gBrowser.addTabsProgressListener(progressListener);
MozLoopService.openFxASettings();
MozLoopService.openFxASettings();
});
yield deferredTab.promise;
while (gBrowser.tabs.length > 1) {
gBrowser.removeTab(gBrowser.tabs[1]);
}

View File

@ -92,38 +92,36 @@ add_task(function* token_request_invalid_state() {
// Helper methods
function promiseParams() {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", BASE_URL + "/fxa-oauth/params", true);
xhr.responseType = "json";
xhr.addEventListener("load", () => {
info("/fxa-oauth/params response:\n" + JSON.stringify(xhr.response, null, 4));
deferred.resolve(xhr);
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", BASE_URL + "/fxa-oauth/params", true);
xhr.responseType = "json";
xhr.addEventListener("load", () => {
info("/fxa-oauth/params response:\n" + JSON.stringify(xhr.response, null, 4));
resolve(xhr);
});
xhr.addEventListener("error", reject);
xhr.send();
});
xhr.addEventListener("error", deferred.reject);
xhr.send();
return deferred.promise;
}
function promiseToken(code, state) {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", BASE_URL + "/fxa-oauth/token", true);
xhr.setRequestHeader("Authorization", "Hawk ...");
xhr.responseType = "json";
xhr.addEventListener("load", () => {
info("/fxa-oauth/token response:\n" + JSON.stringify(xhr.response, null, 4));
deferred.resolve(xhr);
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", BASE_URL + "/fxa-oauth/token", true);
xhr.setRequestHeader("Authorization", "Hawk ...");
xhr.responseType = "json";
xhr.addEventListener("load", () => {
info("/fxa-oauth/token response:\n" + JSON.stringify(xhr.response, null, 4));
resolve(xhr);
});
xhr.addEventListener("error", reject);
let payload = {
code: code,
state: state,
};
xhr.send(JSON.stringify(payload, null, 4));
});
xhr.addEventListener("error", deferred.reject);
let payload = {
code: code,
state: state,
};
xhr.send(JSON.stringify(payload, null, 4));
return deferred.promise;
}

View File

@ -17,58 +17,57 @@ const WAS_OFFLINE = Services.io.offline;
var gMozLoopAPI;
function promiseGetMozLoopAPI() {
let deferred = Promise.defer();
let loopPanel = document.getElementById("loop-notification-panel");
let btn = document.getElementById("loop-call-button");
return new Promise((resolve, reject) => {
let loopPanel = document.getElementById("loop-notification-panel");
let btn = document.getElementById("loop-call-button");
// Wait for the popup to be shown if it's not already, then we can get the iframe and
// wait for the iframe's load to be completed.
if (loopPanel.state == "closing" || loopPanel.state == "closed") {
loopPanel.addEventListener("popupshown", () => {
loopPanel.removeEventListener("popupshown", onpopupshown, true);
onpopupshown();
}, true);
// Wait for the popup to be shown if it's not already, then we can get the iframe and
// wait for the iframe's load to be completed.
if (loopPanel.state == "closing" || loopPanel.state == "closed") {
loopPanel.addEventListener("popupshown", () => {
loopPanel.removeEventListener("popupshown", onpopupshown, true);
onpopupshown();
}, true);
// Now we're setup, click the button.
btn.click();
} else {
setTimeout(onpopupshown, 0);
}
function onpopupshown() {
let iframe = document.getElementById(btn.getAttribute("notificationFrameId"));
if (iframe.contentDocument &&
iframe.contentDocument.readyState == "complete") {
gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop;
deferred.resolve();
// Now we're setup, click the button.
btn.click();
} else {
iframe.addEventListener("load", function panelOnLoad(e) {
iframe.removeEventListener("load", panelOnLoad, true);
setTimeout(onpopupshown, 0);
}
function onpopupshown() {
let iframe = document.getElementById(btn.getAttribute("notificationFrameId"));
if (iframe.contentDocument &&
iframe.contentDocument.readyState == "complete") {
gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop;
// We do this in an execute soon to allow any other event listeners to
// be handled, just in case.
deferred.resolve();
}, true);
}
}
resolve();
} else {
iframe.addEventListener("load", function panelOnLoad(e) {
iframe.removeEventListener("load", panelOnLoad, true);
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
registerCleanupFunction(function() {
loopPanel.hidePopup();
let frameId = btn.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
loopPanel.removeChild(frame);
gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop;
// We do this in an execute soon to allow any other event listeners to
// be handled, just in case.
resolve();
}, true);
}
}
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
registerCleanupFunction(function() {
loopPanel.hidePopup();
let frameId = btn.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
loopPanel.removeChild(frame);
}
});
});
return deferred.promise;
}
/**
@ -105,16 +104,15 @@ function loadLoopPanel(aOverrideOptions = {}) {
}
function promiseOAuthParamsSetup(baseURL, params) {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", baseURL + "/setup_params", true);
xhr.setRequestHeader("X-Params", JSON.stringify(params));
xhr.addEventListener("load", () => deferred.resolve(xhr));
xhr.addEventListener("error", error => deferred.reject(error));
xhr.send();
return deferred.promise;
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("POST", baseURL + "/setup_params", true);
xhr.setRequestHeader("X-Params", JSON.stringify(params));
xhr.addEventListener("load", () => resolve(xhr));
xhr.addEventListener("error", error => reject(error));
xhr.send();
});
}
function* resetFxA() {
@ -149,41 +147,39 @@ function checkLoggedOutState() {
}
function promiseDeletedOAuthParams(baseURL) {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("DELETE", baseURL + "/setup_params", true);
xhr.addEventListener("load", () => deferred.resolve(xhr));
xhr.addEventListener("error", deferred.reject);
xhr.send();
return deferred.promise;
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("DELETE", baseURL + "/setup_params", true);
xhr.addEventListener("load", () => resolve(xhr));
xhr.addEventListener("error", reject);
xhr.send();
});
}
function promiseObserverNotified(aTopic, aExpectedData = null) {
let deferred = Promise.defer();
Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
Services.obs.removeObserver(onNotification, aTopic);
is(aData, aExpectedData, "observer data should match expected data")
deferred.resolve({subject: aSubject, data: aData});
}, aTopic, false);
return deferred.promise;
return new Promise((resolve, reject) => {
Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
Services.obs.removeObserver(onNotification, aTopic);
is(aData, aExpectedData, "observer data should match expected data")
resolve({subject: aSubject, data: aData});
}, aTopic, false);
});
}
/**
* Get the last registration on the test server.
*/
function promiseOAuthGetRegistration(baseURL) {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("GET", baseURL + "/get_registration", true);
xhr.responseType = "json";
xhr.addEventListener("load", () => deferred.resolve(xhr));
xhr.addEventListener("error", deferred.reject);
xhr.send();
return deferred.promise;
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
xhr.open("GET", baseURL + "/get_registration", true);
xhr.responseType = "json";
xhr.addEventListener("load", () => resolve(xhr));
xhr.addEventListener("error", reject);
xhr.send();
});
}
function getLoopString(stringID) {

View File

@ -38,7 +38,10 @@ describe("loop.store.ConversationStore", function () {
navigator.mozLoop = {
getLoopBoolPref: sandbox.stub(),
releaseCallData: sandbox.stub()
calls: {
setCallInProgress: sandbox.stub(),
clearCallInProgress: sandbox.stub()
}
};
dispatcher = new loop.Dispatcher();
@ -156,8 +159,9 @@ describe("loop.store.ConversationStore", function () {
dispatcher.dispatch(
new sharedActions.ConnectionFailure({reason: "fake"}));
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "42");
});
});
@ -221,40 +225,29 @@ describe("loop.store.ConversationStore", function () {
});
});
describe("#gatherCallData", function() {
describe("#setupWindowData", function() {
var fakeSetupWindowData;
beforeEach(function() {
store.set({callState: CALL_STATES.INIT});
navigator.mozLoop = {
getCallData: function() {
return {
contact: contact,
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO
};
}
fakeSetupWindowData = {
windowId: "123456",
type: "outgoing",
contact: contact,
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO
};
});
afterEach(function() {
delete navigator.mozLoop;
});
it("should set the state to 'gather'", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
windowId: "76543218",
outgoing: true
}));
new sharedActions.SetupWindowData(fakeSetupWindowData));
expect(store.get("callState")).eql(CALL_STATES.GATHER);
});
it("should save the basic call information", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
windowId: "123456",
outgoing: true
}));
new sharedActions.SetupWindowData(fakeSetupWindowData));
expect(store.get("windowId")).eql("123456");
expect(store.get("outgoing")).eql(true);
@ -262,28 +255,16 @@ describe("loop.store.ConversationStore", function () {
it("should save the basic information from the mozLoop api", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
windowId: "123456",
outgoing: true
}));
new sharedActions.SetupWindowData(fakeSetupWindowData));
expect(store.get("contact")).eql(contact);
expect(store.get("callType")).eql(sharedUtils.CALL_TYPES.AUDIO_VIDEO);
});
describe("outgoing calls", function() {
var outgoingCallData;
beforeEach(function() {
outgoingCallData = {
windowId: "123456",
outgoing: true
};
});
it("should request the outgoing call data", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData(outgoingCallData));
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
@ -291,7 +272,7 @@ describe("loop.store.ConversationStore", function () {
});
it("should include all email addresses in the call data", function() {
contact = {
fakeSetupWindowData.contact = {
name: [ "Mr Smith" ],
email: [{
type: "home",
@ -306,7 +287,7 @@ describe("loop.store.ConversationStore", function () {
};
dispatcher.dispatch(
new sharedActions.GatherCallData(outgoingCallData));
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
@ -314,7 +295,7 @@ describe("loop.store.ConversationStore", function () {
});
it("should include trim phone numbers for the call data", function() {
contact = {
fakeSetupWindowData.contact = {
name: [ "Mr Smith" ],
tel: [{
type: "home",
@ -324,7 +305,7 @@ describe("loop.store.ConversationStore", function () {
};
dispatcher.dispatch(
new sharedActions.GatherCallData(outgoingCallData));
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
@ -332,7 +313,7 @@ describe("loop.store.ConversationStore", function () {
});
it("should include all email and telephone values in the call data", function() {
contact = {
fakeSetupWindowData.contact = {
name: [ "Mr Smith" ],
email: [{
type: "home",
@ -355,7 +336,7 @@ describe("loop.store.ConversationStore", function () {
};
dispatcher.dispatch(
new sharedActions.GatherCallData(outgoingCallData));
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
@ -375,8 +356,8 @@ describe("loop.store.ConversationStore", function () {
client.setupOutgoingCall.callsArgWith(2, null, callData);
store.gatherCallData(
new sharedActions.GatherCallData(outgoingCallData));
store.setupWindowData(
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(dispatcher.dispatch);
// Can't use instanceof here, as that matches any action
@ -389,8 +370,8 @@ describe("loop.store.ConversationStore", function () {
it("should dispatch a connection failure action on failure", function() {
client.setupOutgoingCall.callsArgWith(2, {});
store.gatherCallData(
new sharedActions.GatherCallData(outgoingCallData));
store.setupWindowData(
new sharedActions.SetupWindowData(fakeSetupWindowData));
sinon.assert.calledOnce(dispatcher.dispatch);
// Can't use instanceof here, as that matches any action
@ -525,8 +506,9 @@ describe("loop.store.ConversationStore", function () {
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.HangupCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "42");
});
});
@ -565,8 +547,9 @@ describe("loop.store.ConversationStore", function () {
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.PeerHungupCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "42");
});
});
@ -613,8 +596,9 @@ describe("loop.store.ConversationStore", function () {
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.CancelCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress);
sinon.assert.calledWithExactly(
navigator.mozLoop.calls.clearCallInProgress, "42");
});
});

View File

@ -22,31 +22,30 @@ describe("loop.Dispatcher", function () {
it("should register a store against an action name", function() {
var object = { fake: true };
dispatcher.register(object, ["gatherCallData"]);
dispatcher.register(object, ["getWindowData"]);
expect(dispatcher._eventData["gatherCallData"][0]).eql(object);
expect(dispatcher._eventData["getWindowData"][0]).eql(object);
});
it("should register multiple store against an action name", function() {
var object1 = { fake: true };
var object2 = { fake2: true };
dispatcher.register(object1, ["gatherCallData"]);
dispatcher.register(object2, ["gatherCallData"]);
dispatcher.register(object1, ["getWindowData"]);
dispatcher.register(object2, ["getWindowData"]);
expect(dispatcher._eventData["gatherCallData"][0]).eql(object1);
expect(dispatcher._eventData["gatherCallData"][1]).eql(object2);
expect(dispatcher._eventData["getWindowData"][0]).eql(object1);
expect(dispatcher._eventData["getWindowData"][1]).eql(object2);
});
});
describe("#dispatch", function() {
var gatherStore1, gatherStore2, cancelStore1, connectStore1;
var gatherAction, cancelAction, connectAction, resolveCancelStore1;
var getDataStore1, getDataStore2, cancelStore1, connectStore1;
var getDataAction, cancelAction, connectAction, resolveCancelStore1;
beforeEach(function() {
gatherAction = new sharedActions.GatherCallData({
windowId: "42",
outgoing: false
getDataAction = new sharedActions.GetWindowData({
windowId: "42"
});
cancelAction = new sharedActions.CancelCall();
@ -54,11 +53,11 @@ describe("loop.Dispatcher", function () {
sessionData: {}
});
gatherStore1 = {
gatherCallData: sinon.stub()
getDataStore1 = {
getWindowData: sinon.stub()
};
gatherStore2 = {
gatherCallData: sinon.stub()
getDataStore2 = {
getWindowData: sinon.stub()
};
cancelStore1 = {
cancelCall: sinon.stub()
@ -67,8 +66,8 @@ describe("loop.Dispatcher", function () {
connectCall: function() {}
};
dispatcher.register(gatherStore1, ["gatherCallData"]);
dispatcher.register(gatherStore2, ["gatherCallData"]);
dispatcher.register(getDataStore1, ["getWindowData"]);
dispatcher.register(getDataStore2, ["getWindowData"]);
dispatcher.register(cancelStore1, ["cancelCall"]);
dispatcher.register(connectStore1, ["connectCall"]);
});
@ -76,33 +75,33 @@ describe("loop.Dispatcher", function () {
it("should dispatch an action to the required object", function() {
dispatcher.dispatch(cancelAction);
sinon.assert.notCalled(gatherStore1.gatherCallData);
sinon.assert.notCalled(getDataStore1.getWindowData);
sinon.assert.calledOnce(cancelStore1.cancelCall);
sinon.assert.calledWithExactly(cancelStore1.cancelCall, cancelAction);
sinon.assert.notCalled(gatherStore2.gatherCallData);
sinon.assert.notCalled(getDataStore2.getWindowData);
});
it("should dispatch actions to multiple objects", function() {
dispatcher.dispatch(gatherAction);
dispatcher.dispatch(getDataAction);
sinon.assert.calledOnce(gatherStore1.gatherCallData);
sinon.assert.calledWithExactly(gatherStore1.gatherCallData, gatherAction);
sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledWithExactly(getDataStore1.getWindowData, getDataAction);
sinon.assert.notCalled(cancelStore1.cancelCall);
sinon.assert.calledOnce(gatherStore2.gatherCallData);
sinon.assert.calledWithExactly(gatherStore2.gatherCallData, gatherAction);
sinon.assert.calledOnce(getDataStore2.getWindowData);
sinon.assert.calledWithExactly(getDataStore2.getWindowData, getDataAction);
});
it("should dispatch multiple actions", function() {
dispatcher.dispatch(cancelAction);
dispatcher.dispatch(gatherAction);
dispatcher.dispatch(getDataAction);
sinon.assert.calledOnce(cancelStore1.cancelCall);
sinon.assert.calledOnce(gatherStore1.gatherCallData);
sinon.assert.calledOnce(gatherStore2.gatherCallData);
sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledOnce(getDataStore2.getWindowData);
});
describe("Queued actions", function() {
@ -110,10 +109,10 @@ describe("loop.Dispatcher", function () {
// Restore the stub, so that we can easily add a function to be
// returned. Unfortunately, sinon doesn't make this easy.
sandbox.stub(connectStore1, "connectCall", function() {
dispatcher.dispatch(gatherAction);
dispatcher.dispatch(getDataAction);
sinon.assert.notCalled(gatherStore1.gatherCallData);
sinon.assert.notCalled(gatherStore2.gatherCallData);
sinon.assert.notCalled(getDataStore1.getWindowData);
sinon.assert.notCalled(getDataStore2.getWindowData);
});
});
@ -132,8 +131,8 @@ describe("loop.Dispatcher", function () {
sinon.assert.calledOnce(connectStore1.connectCall);
// These should be called, because the dispatcher synchronously queues actions.
sinon.assert.calledOnce(gatherStore1.gatherCallData);
sinon.assert.calledOnce(gatherStore2.gatherCallData);
sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledOnce(getDataStore2.getWindowData);
});
});
});

View File

@ -31,20 +31,20 @@ describe("loop.store.LocalRoomStore", function () {
});
});
describe("#setupEmptyRoom", function() {
var store, fakeMozLoop, fakeRoomId, fakeRoomName;
describe("#setupWindowData", function() {
var store, fakeMozLoop, fakeToken, fakeRoomName;
beforeEach(function() {
fakeRoomId = "337-ff-54";
fakeToken = "337-ff-54";
fakeRoomName = "Monkeys";
fakeMozLoop = {
rooms: { getRoomData: sandbox.stub() }
rooms: { get: sandbox.stub() }
};
store = new loop.store.LocalRoomStore(
{mozLoop: fakeMozLoop, dispatcher: dispatcher});
fakeMozLoop.rooms.getRoomData.
withArgs(fakeRoomId).
fakeMozLoop.rooms.get.
withArgs(fakeToken).
callsArgOnWith(1, // index of callback argument
store, // |this| to call it on
null, // args to call the callback with...
@ -57,8 +57,11 @@ describe("loop.store.LocalRoomStore", function () {
done();
});
dispatcher.dispatch(new sharedActions.SetupEmptyRoom(
{localRoomId: fakeRoomId}));
dispatcher.dispatch(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
it("should set localRoomId on the store from the action data",
@ -66,13 +69,16 @@ describe("loop.store.LocalRoomStore", function () {
store.once("change", function () {
expect(store.getStoreState()).
to.have.property('localRoomId', fakeRoomId);
to.have.property('roomToken', fakeToken);
done();
});
dispatcher.dispatch(
new sharedActions.SetupEmptyRoom({localRoomId: fakeRoomId}));
});
dispatcher.dispatch(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
it("should set serverData.roomName from the getRoomData callback",
function(done) {
@ -83,16 +89,19 @@ describe("loop.store.LocalRoomStore", function () {
done();
});
dispatcher.dispatch(
new sharedActions.SetupEmptyRoom({localRoomId: fakeRoomId}));
dispatcher.dispatch(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
it("should set error on the store when getRoomData calls back an error",
function(done) {
var fakeError = new Error("fake error");
fakeMozLoop.rooms.getRoomData.
withArgs(fakeRoomId).
fakeMozLoop.rooms.get.
withArgs(fakeToken).
callsArgOnWith(1, // index of callback argument
store, // |this| to call it on
fakeError); // args to call the callback with...
@ -102,8 +111,11 @@ describe("loop.store.LocalRoomStore", function () {
done();
});
dispatcher.dispatch(
new sharedActions.SetupEmptyRoom({localRoomId: fakeRoomId}));
dispatcher.dispatch(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: fakeToken
}));
});
});

View File

@ -326,4 +326,27 @@ describe("loop.store.RoomListStore", function () {
});
});
});
describe("#openRoom", function() {
var store, fakeMozLoop;
beforeEach(function() {
fakeMozLoop = {
rooms: {
open: sinon.spy()
}
};
store = new loop.store.RoomListStore({
dispatcher: dispatcher,
mozLoop: fakeMozLoop
});
});
it("should open the room via mozLoop", function() {
dispatcher.dispatch(new sharedActions.OpenRoom({roomToken: "42abc"}));
sinon.assert.calledOnce(fakeMozLoop.rooms.open);
sinon.assert.calledWithExactly(fakeMozLoop.rooms.open, "42abc");
});
});
});

View File

@ -4,6 +4,9 @@
Cu.import("resource://services-common/utils.js");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/Chat.jsm");
let openChatOrig = Chat.open;
const kRooms = new Map([
["_nxD4V4FflQ", {
@ -51,6 +54,38 @@ let roomDetail = {
}]
};
const kRoomUpdates = {
"1": {
participants: []
},
"2": {
participants: [{
displayName: "Alexis",
account: "alexis@example.com",
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
}]
},
"3": {
participants: [{
displayName: "Adam",
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
}]
},
"4": {
participants: [{
displayName: "Adam",
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
}, {
displayName: "Alexis",
account: "alexis@example.com",
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
}, {
displayName: "Ruharb",
roomConnectionId: "5de6281c-6568-455f-af08-c0b0a973100e"
}]
}
};
const kCreateRoomProps = {
roomName: "UX Discussion",
expiresIn: 5,
@ -64,6 +99,76 @@ const kCreateRoomData = {
expiresAt: 1405534180
};
const normalizeRoom = function(room) {
delete room.currSize;
if (!("participants" in room)) {
let name = room.roomName;
for (let key of Object.getOwnPropertyNames(roomDetail)) {
room[key] = roomDetail[key];
}
room.roomName = name;
}
return room;
};
const compareRooms = function(room1, room2) {
Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
};
// LoopRooms emits various events. Test if they work as expected here.
let gExpectedAdds = [];
let gExpectedUpdates = [];
let gExpectedJoins = {};
let gExpectedLeaves = {};
const onRoomAdded = function(e, room) {
let expectedIds = gExpectedAdds.map(room => room.roomToken);
let idx = expectedIds.indexOf(room.roomToken);
Assert.ok(idx > -1, "Added room should be expected");
let expected = gExpectedAdds[idx];
compareRooms(room, expected);
gExpectedAdds.splice(idx, 1);
};
const onRoomUpdated = function(e, room) {
let idx = gExpectedUpdates.indexOf(room.roomToken);
Assert.ok(idx > -1, "Updated room should be expected");
gExpectedUpdates.splice(idx, 1);
};
const onRoomJoined = function(e, roomToken, participant) {
let participants = gExpectedJoins[roomToken];
Assert.ok(participants, "Participant should be expected to join");
let idx = participants.indexOf(participant.roomConnectionId);
Assert.ok(idx > -1, "Participant should be expected to join");
participants.splice(idx, 1);
if (!participants.length) {
delete gExpectedJoins[roomToken];
}
};
const onRoomLeft = function(e, roomToken, participant) {
let participants = gExpectedLeaves[roomToken];
Assert.ok(participants, "Participant should be expected to leave");
let idx = participants.indexOf(participant.roomConnectionId);
Assert.ok(idx > -1, "Participant should be expected to leave");
participants.splice(idx, 1);
if (!participants.length) {
delete gExpectedLeaves[roomToken];
}
};
const parseQueryString = function(qs) {
let map = {};
let parts = qs.split("=");
for (let i = 0, l = parts.length; i < l; ++i) {
if (i % 2 === 1) {
map[parts[i - 1]] = parts[i];
}
}
return map;
};
add_task(function* setup_server() {
loopServer.registerPathHandler("/registration", (req, res) => {
res.setStatusLine(null, 200, "OK");
@ -82,7 +187,14 @@ add_task(function* setup_server() {
res.write(JSON.stringify(kCreateRoomData));
} else {
res.write(JSON.stringify([...kRooms.values()]));
if (req.queryString) {
let qs = parseQueryString(req.queryString);
let room = kRooms.get("_nxD4V4FflQ");
room.participants = kRoomUpdates[qs.version].participants;
res.write(JSON.stringify([room]));
} else {
res.write(JSON.stringify([...kRooms.values()]));
}
}
res.processAsync();
@ -116,27 +228,13 @@ add_task(function* setup_server() {
res.processAsync();
res.finish();
});
yield MozLoopService.promiseRegisteredWithServers();
});
const normalizeRoom = function(room) {
delete room.currSize;
if (!("participants" in room)) {
let name = room.roomName;
for (let key of Object.getOwnPropertyNames(roomDetail)) {
room[key] = roomDetail[key];
}
room.roomName = name;
}
return room;
};
const compareRooms = function(room1, room2) {
Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
};
// Test if fetching a list of all available rooms works correctly.
add_task(function* test_getAllRooms() {
yield MozLoopService.promiseRegisteredWithServers();
gExpectedAdds.push(...kRooms.values());
let rooms = yield LoopRooms.promise("getAll");
Assert.equal(rooms.length, 3);
for (let room of rooms) {
@ -144,32 +242,97 @@ add_task(function* test_getAllRooms() {
}
});
// Test if fetching a room works correctly.
add_task(function* test_getRoom() {
yield MozLoopService.promiseRegisteredWithServers();
let roomToken = "_nxD4V4FflQ";
let room = yield LoopRooms.promise("get", roomToken);
Assert.deepEqual(room, kRooms.get(roomToken));
});
// Test if fetching a room with incorrect token or return values yields an error.
add_task(function* test_errorStates() {
yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail");
yield Assert.rejects(LoopRooms.promise("get", "errorMalformed"), /SyntaxError/, "Wrong message format should reject");
});
// Test if creating a new room works as expected.
add_task(function* test_createRoom() {
let eventCalled = false;
LoopRooms.once("add", (e, room) => {
compareRooms(room, kCreateRoomProps);
eventCalled = true;
});
gExpectedAdds.push(kCreateRoomProps);
let room = yield LoopRooms.promise("create", kCreateRoomProps);
compareRooms(room, kCreateRoomProps);
Assert.ok(eventCalled, "Event should have fired");
});
// Test if opening a new room window works correctly.
add_task(function* test_openRoom() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
};
LoopRooms.open("fakeToken");
Assert.ok(openedUrl, "should open a chat window");
// Stop the busy kicking in for following tests.
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
let windowData = MozLoopService.getConversationWindowData(windowId);
Assert.equal(windowData.type, "room", "window data should contain room as the type");
Assert.equal(windowData.roomToken, "fakeToken", "window data should have the roomToken");
});
// Test if push updates function as expected.
add_task(function* test_roomUpdates() {
gExpectedUpdates.push("_nxD4V4FflQ");
gExpectedLeaves["_nxD4V4FflQ"] = [
"2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
"781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
];
roomsPushNotification("1");
yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0);
gExpectedUpdates.push("_nxD4V4FflQ");
gExpectedJoins["_nxD4V4FflQ"] = ["2a1787a6-4a73-43b5-ae3e-906ec1e763cb"];
roomsPushNotification("2");
yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
gExpectedUpdates.push("_nxD4V4FflQ");
gExpectedJoins["_nxD4V4FflQ"] = ["781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"];
gExpectedLeaves["_nxD4V4FflQ"] = ["2a1787a6-4a73-43b5-ae3e-906ec1e763cb"];
roomsPushNotification("3");
yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0);
gExpectedUpdates.push("_nxD4V4FflQ");
gExpectedJoins["_nxD4V4FflQ"] = [
"2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
"5de6281c-6568-455f-af08-c0b0a973100e"];
roomsPushNotification("4");
yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
});
// 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");
Assert.strictEqual(gExpectedUpdates.length, 0, "No room updates should be expected anymore");
});
function run_test() {
setupFakeLoopServer();
LoopRooms.on("add", onRoomAdded);
LoopRooms.on("update", onRoomUpdated);
LoopRooms.on("joined", onRoomJoined);
LoopRooms.on("left", onRoomLeft);
do_register_cleanup(function () {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
LoopRooms.off("add", onRoomAdded);
LoopRooms.off("update", onRoomUpdated);
LoopRooms.off("joined", onRoomJoined);
LoopRooms.off("left", onRoomLeft);
});
run_next_test();
}

View File

@ -26,8 +26,10 @@ add_test(function test_busy_2guest_calls() {
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = 0;
Chat.open = function() {
let windowId;
Chat.open = function(contentWindow, origin, title, url) {
opened++;
windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
@ -35,7 +37,7 @@ add_test(function test_busy_2guest_calls() {
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
do_check_true(opened === 1, "should open only one chat window");
do_check_true(actionReceived, "should respond with busy/reject to second call");
LoopCalls.releaseCallData(firstCallId);
LoopCalls.clearCallInProgress(windowId);
run_next_test();
}, () => {
do_throw("should have opened a chat window for first call and rejected second call");
@ -49,8 +51,10 @@ add_test(function test_busy_1fxa_1guest_calls() {
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = 0;
Chat.open = function() {
let windowId;
Chat.open = function(contentWindow, origin, title, url) {
opened++;
windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
@ -59,7 +63,7 @@ add_test(function test_busy_1fxa_1guest_calls() {
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
do_check_true(opened === 1, "should open only one chat window");
do_check_true(actionReceived, "should respond with busy/reject to second call");
LoopCalls.releaseCallData(firstCallId);
LoopCalls.clearCallInProgress(windowId);
run_next_test();
}, () => {
do_throw("should have opened a chat window for first call and rejected second call");
@ -73,8 +77,10 @@ add_test(function test_busy_2fxa_calls() {
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = 0;
Chat.open = function() {
let windowId;
Chat.open = function(contentWindow, origin, title, url) {
opened++;
windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
@ -82,7 +88,7 @@ add_test(function test_busy_2fxa_calls() {
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
do_check_true(opened === 1, "should open only one chat window");
do_check_true(actionReceived, "should respond with busy/reject to second call");
LoopCalls.releaseCallData(firstCallId);
LoopCalls.clearCallInProgress(windowId);
run_next_test();
}, () => {
do_throw("should have opened a chat window for first call and rejected second call");
@ -96,8 +102,10 @@ add_test(function test_busy_1guest_1fxa_calls() {
MozLoopService.promiseRegisteredWithServers().then(() => {
let opened = 0;
Chat.open = function() {
let windowId;
Chat.open = function(contentWindow, origin, title, url) {
opened++;
windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
@ -106,7 +114,7 @@ add_test(function test_busy_1guest_1fxa_calls() {
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
do_check_true(opened === 1, "should open only one chat window");
do_check_true(actionReceived, "should respond with busy/reject to second call");
LoopCalls.releaseCallData(firstCallId);
LoopCalls.clearCallInProgress(windowId);
run_next_test();
}, () => {
do_throw("should have opened a chat window for first call and rejected second call");

View File

@ -26,11 +26,11 @@ add_task(function test_startDirectCall_opens_window() {
do_check_true(!!openedUrl, "should open a chat window");
// Stop the busy kicking in for following tests.
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
LoopCalls.releaseCallData(callId);
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
LoopCalls.clearCallInProgress(windowId);
});
add_task(function test_startDirectCall_getCallData() {
add_task(function test_startDirectCall_getConversationWindowData() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
@ -38,15 +38,15 @@ add_task(function test_startDirectCall_getCallData() {
LoopCalls.startDirectCall(contact, "audio-video");
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
let callData = LoopCalls.getCallData(callId);
let callData = MozLoopService.getConversationWindowData(windowId);
do_check_eq(callData.callType, "audio-video", "should have the correct call type");
do_check_eq(callData.contact, contact, "should have the contact details");
// Stop the busy kicking in for following tests.
LoopCalls.releaseCallData(callId);
LoopCalls.clearCallInProgress(windowId);
});
function run_test() {

View File

@ -2352,7 +2352,12 @@ let E10SUINotification = {
checkStatus: function() {
let skipE10sChecks = false;
try {
// This order matters, because
// browser.tabs.remote.autostart.disabled-because-using-a11y is not
// always defined and will throw when not present.
// privacy.trackingprotection.enabled is always defined.
skipE10sChecks = (UpdateChannel.get() != "nightly") ||
Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
Services.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y");
} catch(e) {}

View File

@ -4,7 +4,7 @@ ac_add_options --with-l10n-base=../../../l10n
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
if test "${MOZ_UPDATE_CHANNEL}" = "nightly" -o "${MOZ_UPDATE_CHANNEL}" = "aurora"; then
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi

View File

@ -9,7 +9,7 @@ ac_add_options --enable-dtrace
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
if test "${MOZ_UPDATE_CHANNEL}" = "nightly" -o "${MOZ_UPDATE_CHANNEL}" = "aurora"; then
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi

View File

@ -9,7 +9,7 @@ ac_add_options --with-google-oauth-api-keyfile=/builds/google-oauth-api.key
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
if test "${MOZ_UPDATE_CHANNEL}" = "nightly" -o "${MOZ_UPDATE_CHANNEL}" = "aurora"; then
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi

View File

@ -11,7 +11,7 @@ ac_add_options --disable-unified-compilation
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1
if test "${MOZ_UPDATE_CHANNEL}" = "nightly" -o "${MOZ_UPDATE_CHANNEL}" = "aurora"; then
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi

View File

@ -48,7 +48,7 @@ whitelist['nightly']['linux64'] += [
]
whitelist['nightly']['macosx-universal'] += [
'if test "${MOZ_UPDATE_CHANNEL}" = "nightly" -o "${MOZ_UPDATE_CHANNEL}" = "aurora"; then',
'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
'ac_add_options --with-macbundlename-prefix=Firefox',
'fi',
'mk_add_options MOZ_MAKE_FLAGS="-j12"',

View File

@ -1817,7 +1817,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 64, 16, 48);
}
.tab-close-button:not([selected]):not(:hover):-moz-lwtheme-brighttext {
#TabsToolbar[brighttext] .tab-close-button:not([selected]):not(:hover) {
background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
}

View File

@ -3,3 +3,61 @@
% file, You can obtain one at http://mozilla.org/MPL/2.0/.
%include ../shared/devedition.inc.css
.tab-close-button[selected]:not(:hover) {
background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
}
/* The menubar should match the dark theme */
#toolbar-menubar {
-moz-appearance: none;
}
#main-menubar {
color: var(--chrome-color);
}
#main-menubar > menu:not([open]) {
color: inherit;
}
/* Allow buttons with -moz-appearance set to look normal on hover and open states */
#navigator-toolbox .toolbarbutton-1:not(.subviewbutton):-moz-any(:hover, [open="true"]),
toolbarbutton.bookmark-item:not(.subviewbutton):-moz-any(:hover, [open="true"]) {
color: initial;
}
/* Square back and forward buttons */
#back-button:not(:-moz-lwtheme) > .toolbarbutton-icon,
#forward-button:not(:-moz-lwtheme) > .toolbarbutton-icon {
margin: 0;
border: none;
padding: 2px 6px;
background: #252C33;
box-shadow: none !important;
}
/* Override a box shadow for disabled back button */
#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
box-shadow: none !important;
}
#back-button:hover:not([disabled="true"]) > .toolbarbutton-icon,
#forward-button:hover:not([disabled="true"]) > .toolbarbutton-icon {
background: #1B2127 !important;
}
#back-button > .toolbarbutton-icon {
border-radius: 2px 0 0 2px !important;
}
.urlbar-history-dropmarker {
-moz-appearance: none;
padding: 0 3px;
list-style-image: var(--urlbar-dropmarker-url);
-moz-image-region: var(--urlbar-dropmarker-region);
}
/* Add the proper background for tab overflow */
#alltabs-button,
#new-tab-button {
background: var(--chrome-background-color);
}

View File

@ -198,6 +198,8 @@ browser.jar:
skin/classic/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
skin/classic/browser/devedition/search.svg (../shared/devedition/search.svg)
skin/classic/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

View File

@ -26,6 +26,7 @@
0 1px 0 hsla(0,0%,100%,.5) inset;
--toolbarbutton-active-background: hsla(0,0%,0%,.02) linear-gradient(hsla(0,0%,0%,.12), transparent) border-box;
--toolbarbutton-active-bordercolor: hsla(0,0%,0%,.3);
--toolbarbutton-active-boxshadow: 0 1px 0 hsla(0,0%,100%,.5),
0 1px 0 hsla(0,0%,0%,.05) inset,
0 1px 1px hsla(0,0%,0%,.2) inset;
@ -606,7 +607,7 @@ toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open]))[b
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[buttonover])):hover:active > .toolbarbutton-menubutton-dropmarker,
toolbar .toolbarbutton-1[type="menu-button"][open]:not([disabled]) > .toolbarbutton-menubutton-dropmarker {
background: var(--toolbarbutton-active-background);
border-color: hsla(0,0%,0%,.3);
border-color: var(--toolbarbutton-active-bordercolor);
box-shadow: var(--toolbarbutton-active-boxshadow);
transition-duration: 10ms;
}
@ -766,7 +767,7 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
}
#webide-button@toolbarButtonPressed@ {
-moz-image-region: rect(18px, 342px, 36px, 324px);
-moz-image-region: rect(18px, 738px, 36px, 720px);
}
#new-tab-button@toolbarButtonPressed@ {

View File

@ -3,3 +3,85 @@
% file, You can obtain one at http://mozilla.org/MPL/2.0/.
%include ../shared/devedition.inc.css
/* Include extra space on left/right for dragging since there is no space above
the tabs */
#main-window[tabsintitlebar] #TabsToolbar {
padding-left: 50px;
padding-right: 50px;
margin-bottom: 0; /* Don't overlap the inner highlight at the top of the nav-bar */
}
/* Get rid of 1px bright strip at the top of window */
#main-window[tabsintitlebar] #titlebar-content {
background: var(--chrome-background-color);
}
/* Resize things so that the native titlebar is in line with the tabs */
#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-buttonbox-container,
#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
margin-top: 6px;
}
/* Square back and forward buttons. Need !important on these because there
are a lot of more specific selectors sprinkled around elsewhere for changing
background / shadows for different states */
#back-button,
#forward-button {
height: 22px !important;
box-shadow: none !important;
border: none !important;
background: #252C33 !important;
}
#back-button:hover:not([disabled="true"]),
#forward-button:hover:not([disabled="true"]) {
background: #1B2127 !important;
}
#back-button {
border-radius: 3px 0 0 3px !important;
padding: 0 !important;
margin: 0 !important;
}
#back-button:hover:active:not([disabled="true"]) {
-moz-image-region: rect(18px, 54px, 36px, 36px);
}
/* Use smaller back button icon */
@media (min-resolution: 2dppx) {
#back-button {
-moz-image-region: rect(0, 108px, 36px, 72px);
}
#back-button:hover:active:not([disabled="true"]) {
-moz-image-region: rect(36px, 108px, 72px, 72px);
}
}
#forward-button:hover:active:not(:-moz-lwtheme) {
background-image: none;
box-shadow: none;
}
/* Use forward-facing magnifying glass for the search box */
.search-go-button {
list-style-image: url("chrome://browser/skin/devedition/search.svg#search-icon-mac-inverted");
}
/* Don't use default colors when in full screen */
#main-window:not([customizing]) #navigator-toolbox[inFullscreen] > #TabsToolbar:not(:-moz-lwtheme) {
-moz-appearance: none;
}
/* Tab styling - make sure to use an inverted icon for the selected tab
(brighttext only covers the unselected tabs) */
.tab-close-button[selected=true]:not(:hover) {
-moz-image-region: rect(0, 64px, 16px, 48px);
}
@media (min-resolution: 2dppx) {
.tab-close-button[selected=true]:not(:hover) {
-moz-image-region: rect(0, 128px, 32px, 96px);
}
}

View File

@ -319,6 +319,8 @@ browser.jar:
skin/classic/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
skin/classic/browser/devedition/search.svg (../shared/devedition/search.svg)
skin/classic/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

View File

@ -20,6 +20,15 @@
%include ../browser.inc
:root {
--panel-ui-button-background-image: linear-gradient(to bottom, transparent, hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, transparent),
linear-gradient(to bottom, transparent, hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, transparent),
linear-gradient(to bottom, transparent, hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, transparent);
--panel-ui-button-background-size: 1px calc(100% - 1px), 1px calc(100% - 1px), 1px calc(100% - 1px) !important;
--panel-ui-button-background-position: 0px 0px, 1px 0px, 2px 0px;
--panel-ui-button-background-repeat: no-repeat;
}
#PanelUI-popup #PanelUI-contents:empty {
height: 128px;
}
@ -87,12 +96,10 @@
}
#PanelUI-button {
background-image: linear-gradient(to bottom, transparent, hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, transparent),
linear-gradient(to bottom, transparent, hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, transparent),
linear-gradient(to bottom, transparent, hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, transparent);
background-size: 1px calc(100% - 1px), 1px calc(100% - 1px), 1px calc(100% - 1px) !important;
background-position: 0px 0px, 1px 0px, 2px 0px;
background-repeat: no-repeat;
background-image: var(--panel-ui-button-background-image);
background-size: var(--panel-ui-button-background-size);
background-position: var(--panel-ui-button-background-position);
background-repeat: var(--panel-ui-button-background-repeat);
}
#PanelUI-button:-moz-locale-dir(rtl) {

View File

@ -1,3 +1,227 @@
% 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/.
/* devedition.css is loaded in browser.xul after browser.css when it is
preffed on. The bulk of the styling is here in the shared file, but
there are overrides for each platform in their devedition.css files. */
:root {
/* Chrome */
--space-above-tabbar: 1px;
--chrome-background-color: #1C2126;
--chrome-color: #F5F7FA;
--chrome-secondary-background-color: #39424D;
--chrome-navigator-toolbox-separator-color: rgba(0,0,0,.2);
/* Tabs */
--tabs-toolbar-color: #F5F7FA;
--tab-background-color: #1C2126;
--tab-color: #ced3d9;
--tab-hover-background-color: hsla(206,37%,4%,.5);
--tab-separator-color: #464C50;
--tab-selection-color: #f5f7fa;
--tab-selection-background-color: #1a4666;
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
0 8px 3px -5px #2b82bf inset,
0 -2px 0 rgba(0,0,0,.2) inset;
/* Toolbar buttons */
--toolbarbutton-hover-background: rgba(25,33, 38,.6) linear-gradient(rgba(25,33,38,.6), rgba(25,33,38,.6)) padding-box;
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-hover-bordercolor: rgba(25,33,38,.6);
--toolbarbutton-active-background: rgba(25,33,38,1) linear-gradient(rgba(25,33,38,1), rgba(25,33,38,1)) border-box;
--toolbarbutton-active-boxshadow: none;
--toolbarbutton-active-bordercolor: rgba(25,33,38,.8);
--toolbarbutton-checkedhover-backgroundcolor: #1D4F73;
--toolbarbutton-combined-boxshadow: none;
--toolbarbutton-combined-backgroundimage: linear-gradient(#5F6670 0, #5F6670 18px);
--toolbarbutton-text-shadow: none;
/* Identity box */
--identity-box-chrome-color: #46afe3;
--identity-box-chrome-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
--identity-box-verified-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
--verified-identity-box-backgroundcolor: transparent;
/* Url and search bars */
--url-and-searchbar-background-color: #171B1F;
--url-and-searchbar-color: #fff;
--urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-active-region: rect(0px, 22px, 14px, 11px);
--urlbar-dropmarker-2x-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-active-2x-region: rect(0px, 22px, 14px, 11px);
/* Menu button separator */
--panel-ui-button-background-image: linear-gradient(to bottom, #5E6670, #5E6670);
--panel-ui-button-background-size: 1px calc(100% - 1px);
--panel-ui-button-background-position: 1px 0px;
}
.searchbar-dropmarker-image {
--searchbar-dropmarker-url: url("chrome://browser/skin/devtools/dropmarker.svg");
--searchbar-dropmarker-2x-url: url("chrome://browser/skin/devtools/dropmarker.svg");
}
/* Give some space to drag the window around while customizing
(normal space to left and right of tabs doesn't work in this case) */
#main-window[tabsintitlebar][customizing] {
--space-above-tabbar: 9px;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
padding-left: 0;
padding-right: 0;
}
#navigator-toolbox ::-moz-selection {
background-color: #074D75;
color: #fff;
}
/* Change the base colors for the browser chrome */
#tabbrowser-tabs,
#TabsToolbar,
#browser-panel {
background: var(--chrome-background-color);
color: var(--chrome-color);
}
#navigator-toolbox::after {
background: var(--chrome-navigator-toolbox-separator-color)
}
#navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
#browser-bottombox {
background: var(--chrome-secondary-background-color) !important;
color: var(--chrome-color);
}
#navigator-toolbox .toolbarbutton-1:not(.subviewbutton):not(:hover):not([open]),
toolbarbutton.bookmark-item:not(.subviewbutton):not(:hover):not([open]) {
color: var(--chrome-color);
text-shadow: var(--toolbarbutton-text-shadow);
}
/* Using toolbar[brighttext] instead of important to override linux */
toolbar[brighttext] #downloads-indicator-counter {
text-shadow: var(--toolbarbutton-text-shadow);
color: var(--chrome-color);
}
#TabsToolbar {
text-shadow: none !important;
color: var(--chrome-color) !important; /* Make sure that the brighttext attribute is added */
}
/* URL bar and search bar*/
.searchbar-textbox,
#urlbar {
background-color: var(--url-and-searchbar-background-color) !important;
background-image: none !important;
color: var(--url-and-searchbar-color);
border: none !important;
box-shadow: none !important;
}
window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
overflow: -moz-hidden-unscrollable;
clip-path: none;
-moz-margin-start: 0;
}
/* Make the white notication box stick out less. */
#notification-popup-box {
border-radius: 0;
border: none;
background: transparent;
}
/* Nav bar specific stuff */
#nav-bar {
margin-top: 0 !important;
border: none !important;
border-radius: 0 !important;
box-shadow: 0 -1px var(--chrome-navigator-toolbox-separator-color) !important;
background-image: none !important;
}
/* No extra vertical padding for nav bar */
#nav-bar-customization-target,
#nav-bar {
padding-top: 0;
padding-bottom: 0;
}
/* Use smaller back button icon */
#back-button {
-moz-image-region: rect(0, 54px, 18px, 36px);
}
.search-go-button {
/* !important is needed because searchbar.css is loaded after this */
-moz-image-region: auto !important;
list-style-image: url("chrome://browser/skin/devedition/search.svg#search-icon-inverted");
}
.tab-background {
visibility: hidden;
}
#tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
.tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
background-image: linear-gradient(to top, #474C50, #474C50);
background-position: 1px 0;
background-repeat: no-repeat;
background-size: 1px 100%;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down,
.tabbrowser-arrowscrollbox > .scrollbutton-up {
background-color: var(--tab-background-color);
border-color: transparent;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
margin-bottom: 0;
}
.tabbrowser-tab {
/* We normally rely on other tab elements for pointer events, but this
theme hides those so we need it set here instead */
pointer-events: auto;
color: var(--tab-color);
background-color: var(--tab-background-color);
}
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
.tabbrowser-tab:hover {
background-color: var(--tab-hover-background-color);
color: var(--tab-hover-color);
}
.tabbrowser-tab[selected] {
color: var(--tab-selection-color);
background-color: var(--tab-selection-background-color);
box-shadow: var(--tab-selection-box-shadow);
}
/* New tab buttons */
#TabsToolbar > #new-tab-button,
.tabs-newtab-button {
background-image: none !important;
margin: 0 !important;
width: 35px !important;
}
#TabsToolbar > #new-tab-button:hover,
.tabs-newtab-button:hover {
/* Important needed because !important is used in browser.css */
background-color: var(--tab-hover-background-color) !important;
}

View File

@ -0,0 +1,41 @@
<?xml version="1.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/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
width="16"
height="16"
xml:space="preserve">
<style>
use:not(:target) {
display: none;
}
use {
fill: #797C80;
}
use[id*="-inverted"] {
fill: #B6BABF;
}
use[id*="-mac"] {
transform: translate(16px) scaleX(-1);
}
</style>
<defs style="display: none;">
<path id="search" fill-rule="evenodd" clip-rule="evenodd" d="M9.356,1.178c-3.014,0-5.458,2.45-5.458,5.472c0,1.086,0.32,2.096,0.864,2.947
l-3.279,3.287c-0.396,0.397-0.396,1.041,0,1.438l0.202,0.202c0.396,0.397,1.039,0.397,1.435,0l3.275-3.283
c0.854,0.554,1.869,0.88,2.962,0.88c3.014,0,5.458-2.45,5.458-5.471C14.814,3.627,12.371,1.178,9.356,1.178z M9.356,10.001
c-1.847,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352c1.846,0,3.344,1.501,3.344,3.352
C12.7,8.501,11.203,10.001,9.356,10.001z"/>
</defs>
<use id="search-icon" xlink:href="#search"/>
<use id="search-icon-inverted" xlink:href="#search"/>
<use id="search-icon-mac" xlink:href="#search"/>
<use id="search-icon-mac-inverted" xlink:href="#search"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="33" height="14" viewBox="0 0 33 14" enable-background="new 0 0 33 14">
<defs style="display: none;">
<polygon points="0,0 5.5,7 11,0" id="dropmarker-shape"/>
</defs>
<style>
use {
fill: #B6BABF;
}
.hover {
fill: #61BDEB;
}
.active {
fill: #39ACE6;
}
</style>
<use xlink:href="#dropmarker-shape" style="transform: translate(0, 4px)"></use>
<use xlink:href="#dropmarker-shape" style="transform: translate(11px, 4px)" class="hover"></use>
<use xlink:href="#dropmarker-shape" style="transform: translate(22px, 4px)" class="active"></use>
</svg>

After

Width:  |  Height:  |  Size: 691 B

View File

@ -3,3 +3,98 @@
% file, You can obtain one at http://mozilla.org/MPL/2.0/.
%include ../shared/devedition.inc.css
#TabsToolbar::after {
display: none;
}
#back-button > .toolbarbutton-icon,
#forward-button > .toolbarbutton-icon {
background: #252C33 !important;
border-radius: 0 !important;
width: auto !important;
height: auto !important;
padding: 2px 6px !important;
margin: 0 !important;
border: none !important;
box-shadow: none !important;
}
#back-button > .toolbarbutton-icon {
border-radius: 2px 0 0 2px !important;
}
#nav-bar .toolbarbutton-1:not([type=menu-button]),
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
padding-top: 2px;
padding-bottom: 2px;
}
#browser-panel,
#titlebar-content {
background: transparent;
}
/* Ensure that the entire background is styled when maximized */
#main-window[sizemode="maximized"] #browser-panel {
background: var(--chrome-background-color) !important;
}
/* The menu items need to be visible when the entire background is styled */
#main-window[sizemode="maximized"] #main-menubar {
color: var(--chrome-color);
background-color: transparent;
}
#main-window[sizemode="maximized"] #main-menubar > menu:not(:-moz-window-inactive) {
color: inherit;
}
/* Restore draggable space on the sides of tabs when maximized */
#main-window[sizemode="maximized"] .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
padding-left: 15px;
padding-right: 15px;
}
@media (-moz-windows-classic) {
#main-window[sizemode="maximized"] #TabsToolbar {
background: transparent;
}
}
#toolbar-menubar {
text-shadow: none !important;
}
#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
margin-top: 22px;
}
/* Enough space so that the dark background doesn't begin until after the
* window resize controls end on Aero Basic, which has different positioning for the
* window caption buttons, and therefore needs to be special-cased.
*/
@media (-moz-windows-default-theme) {
@media not all and (-moz-windows-compositor) {
#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
margin-top: 28px;
}
}
}
.searchbar-dropmarker-image {
/* Reset image-region from the windows theme */
-moz-image-region: auto !important;
/* Add margin otherwise it looks weird */
-moz-margin-start: 2px;
}
/* Tab styling - make sure to use an inverted icon for the selected tab
(brighttext only covers the unselected tabs) */
.tab-close-button[selected=true]:not(:hover) {
-moz-image-region: rect(0, 64px, 16px, 48px);
}

View File

@ -233,6 +233,8 @@ browser.jar:
skin/classic/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
skin/classic/browser/devedition/search.svg (../shared/devedition/search.svg)
skin/classic/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
@ -664,6 +666,8 @@ browser.jar:
skin/classic/aero/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
skin/classic/aero/browser/translation-16.png (../shared/translation/translation-16.png)
skin/classic/aero/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
skin/classic/aero/browser/devedition/search.svg (../shared/devedition/search.svg)
skin/classic/aero/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
* skin/classic/aero/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/aero/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/aero/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

View File

@ -258,6 +258,9 @@ if test -n "$gonkdir" ; then
elif test -d "$gonkdir/external/bluetooth/bluedroid"; then
MOZ_B2G_BT=1
MOZ_B2G_BT_BLUEDROID=1
if test -d "$gonkdir/system/bluetoothd"; then
MOZ_B2G_BT_DAEMON=1
fi
fi
MOZ_RTSP=1
@ -275,6 +278,9 @@ if test -n "$gonkdir" ; then
MOZ_B2G_CAMERA=1
MOZ_B2G_BT=1
MOZ_B2G_BT_BLUEDROID=1
if test -d "$gonkdir/system/bluetoothd"; then
MOZ_B2G_BT_DAEMON=1
fi
MOZ_NFC=1
MOZ_RTSP=1
MOZ_OMX_DECODER=1

View File

@ -2186,6 +2186,10 @@ GK_ATOM(Home, "Home")
GK_ATOM(Clear, "Clear")
GK_ATOM(VolumeUp, "VolumeUp")
GK_ATOM(VolumeDown, "VolumeDown")
GK_ATOM(NextTrack, "NextTrack")
GK_ATOM(PreviousTrack, "PreviousTrack")
GK_ATOM(MediaStop, "MediaStop")
GK_ATOM(PlayPause, "PlayPause")
GK_ATOM(Menu, "Menu")
GK_ATOM(New, "New")
GK_ATOM(Open, "Open")

View File

@ -208,6 +208,22 @@ DOMInterfaces = {
'headerFile': 'DOMCameraManager.h'
},
'CameraRecorderAudioProfile': {
'headerFile': 'DOMCameraCapabilities.h'
},
'CameraRecorderProfile': {
'headerFile': 'DOMCameraCapabilities.h'
},
'CameraRecorderProfiles': {
'headerFile': 'DOMCameraCapabilities.h'
},
'CameraRecorderVideoProfile': {
'headerFile': 'DOMCameraCapabilities.h'
},
'CanvasRenderingContext2D': {
'implicitJSContext': [
'createImageData', 'getImageData'

View File

@ -8,6 +8,9 @@
#ifdef MOZ_B2G_BT_BLUEDROID
#include "BluetoothHALInterface.h"
#endif
#ifdef MOZ_B2G_BT_DAEMON
#include "BluetoothDaemonInterface.h"
#endif
BEGIN_BLUETOOTH_NAMESPACE
@ -89,15 +92,19 @@ BluetoothInterface*
BluetoothInterface::GetInstance()
{
/* Here's where we decide which implementation to use. Currently
* there is only Bluedroid, but others are possible. Having multiple
* interfaces built-in and selecting the correct one at runtime could
* also be an option.
* there is only Bluedroid and the Bluetooth daemon, but others are
* possible. Having multiple interfaces built-in and selecting the
* correct one at runtime could also be an option.
*/
#ifdef MOZ_B2G_BT_BLUEDROID
return BluetoothHALInterface::GetInstance();
#else
#ifdef MOZ_B2G_BT_DAEMON
return BluetoothDaemonInterface::GetInstance();
#else
return nullptr;
#endif
#endif
}
BluetoothInterface::BluetoothInterface()

View File

@ -53,6 +53,13 @@
/**
* B2G bluedroid:
* MOZ_B2G_BT and MOZ_B2G_BT_BLUEDROID are both defined;
* MOZ_B2G_BLUEZ or MOZ_B2G_DAEMON are not defined.
*/
#include "BluetoothServiceBluedroid.h"
#elif defined(MOZ_B2G_BT_DAEMON)
/**
* B2G Bluetooth daemon:
* MOZ_B2G_BT, MOZ_B2G_BLUEDROID and MOZ_B2G_BT_DAEMON are defined;
* MOZ_B2G_BLUEZ is not defined.
*/
#include "BluetoothServiceBluedroid.h"
@ -249,6 +256,8 @@ BluetoothService::Create()
return new BluetoothDBusService();
#elif defined(MOZ_B2G_BT_BLUEDROID)
return new BluetoothServiceBluedroid();
#elif defined(MOZ_B2G_BT_DAEMON)
return new BluetoothServiceBluedroid();
#endif
#elif defined(MOZ_BLUETOOTH_DBUS)
return new BluetoothDBusService();
@ -582,7 +591,7 @@ BluetoothService::HandleSettingsChanged(nsISupports* aSubject)
MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
return NS_ERROR_UNEXPECTED;
}
SWITCH_BT_DEBUG(setting.mValue.toBoolean());
return NS_OK;

View File

@ -0,0 +1,772 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothDaemonHelpers.h"
#include <limits>
#define MAX_UUID_SIZE 16
BEGIN_BLUETOOTH_NAMESPACE
//
// Conversion
//
nsresult
Convert(bool aIn, uint8_t& aOut)
{
static const bool sValue[] = {
CONVERT(false, 0x00),
CONVERT(true, 0x01)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sValue))) {
aOut = 0;
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sValue[aIn];
return NS_OK;
}
nsresult
Convert(bool aIn, BluetoothScanMode& aOut)
{
static const BluetoothScanMode sScanMode[] = {
CONVERT(false, SCAN_MODE_CONNECTABLE),
CONVERT(true, SCAN_MODE_CONNECTABLE_DISCOVERABLE)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sScanMode))) {
aOut = SCAN_MODE_NONE; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sScanMode[aIn];
return NS_OK;
}
nsresult
Convert(int aIn, int16_t& aOut)
{
if (NS_WARN_IF(aIn < std::numeric_limits<int16_t>::min()) ||
NS_WARN_IF(aIn > std::numeric_limits<int16_t>::max())) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = static_cast<int16_t>(aIn);
return NS_OK;
}
nsresult
Convert(uint8_t aIn, bool& aOut)
{
static const bool sBool[] = {
CONVERT(0x00, false),
CONVERT(0x01, true)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sBool))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sBool[aIn];
return NS_OK;
}
nsresult
Convert(uint8_t aIn, BluetoothAclState& aOut)
{
static const BluetoothAclState sAclState[] = {
CONVERT(0x00, ACL_STATE_CONNECTED),
CONVERT(0x01, ACL_STATE_DISCONNECTED),
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sAclState))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sAclState[aIn];
return NS_OK;
}
nsresult
Convert(uint8_t aIn, BluetoothBondState& aOut)
{
static const BluetoothBondState sBondState[] = {
CONVERT(0x00, BOND_STATE_NONE),
CONVERT(0x01, BOND_STATE_BONDING),
CONVERT(0x02, BOND_STATE_BONDED)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sBondState))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sBondState[aIn];
return NS_OK;
}
nsresult
Convert(int32_t aIn, BluetoothDeviceType& aOut)
{
static const BluetoothDeviceType sDeviceType[] = {
CONVERT(0x00, static_cast<BluetoothDeviceType>(0)), // invalid, required by gcc
CONVERT(0x01, DEVICE_TYPE_BREDR),
CONVERT(0x02, DEVICE_TYPE_BLE),
CONVERT(0x03, DEVICE_TYPE_DUAL)
};
if (NS_WARN_IF(!aIn) ||
NS_WARN_IF(static_cast<size_t>(aIn) >= MOZ_ARRAY_LENGTH(sDeviceType))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sDeviceType[aIn];
return NS_OK;
}
nsresult
Convert(uint8_t aIn, BluetoothPropertyType& aOut)
{
static const BluetoothPropertyType sPropertyType[] = {
CONVERT(0x00, static_cast<BluetoothPropertyType>(0)), // invalid, required by gcc
CONVERT(0x01, PROPERTY_BDNAME),
CONVERT(0x02, PROPERTY_BDADDR),
CONVERT(0x03, PROPERTY_UUIDS),
CONVERT(0x04, PROPERTY_CLASS_OF_DEVICE),
CONVERT(0x05, PROPERTY_TYPE_OF_DEVICE),
CONVERT(0x06, PROPERTY_SERVICE_RECORD),
CONVERT(0x07, PROPERTY_ADAPTER_SCAN_MODE),
CONVERT(0x08, PROPERTY_ADAPTER_BONDED_DEVICES),
CONVERT(0x09, PROPERTY_ADAPTER_DISCOVERY_TIMEOUT),
CONVERT(0x0a, PROPERTY_REMOTE_FRIENDLY_NAME),
CONVERT(0x0b, PROPERTY_REMOTE_RSSI),
CONVERT(0x0c, PROPERTY_REMOTE_VERSION_INFO)
};
if (aIn == 0xff) {
/* This case is handled separately to not populate
* |sPropertyType| with empty entries. */
aOut = PROPERTY_REMOTE_DEVICE_TIMESTAMP;
return NS_OK;
}
if (NS_WARN_IF(!aIn) ||
NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sPropertyType))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sPropertyType[aIn];
return NS_OK;
}
nsresult
Convert(BluetoothSocketType aIn, uint8_t& aOut)
{
static const uint8_t sSocketType[] = {
CONVERT(0, 0), // silences compiler warning
CONVERT(BluetoothSocketType::RFCOMM, 0x01),
CONVERT(BluetoothSocketType::SCO, 0x02),
CONVERT(BluetoothSocketType::L2CAP, 0x03)
// EL2CAP not supported
};
if (NS_WARN_IF(aIn == BluetoothSocketType::EL2CAP) ||
NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sSocketType)) ||
NS_WARN_IF(!sSocketType[aIn])) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sSocketType[aIn];
return NS_OK;
}
nsresult
Convert(int32_t aIn, BluetoothScanMode& aOut)
{
static const BluetoothScanMode sScanMode[] = {
CONVERT(0x00, SCAN_MODE_NONE),
CONVERT(0x01, SCAN_MODE_CONNECTABLE),
CONVERT(0x02, SCAN_MODE_CONNECTABLE_DISCOVERABLE)
};
if (NS_WARN_IF(aIn < 0) ||
NS_WARN_IF(static_cast<size_t>(aIn) >= MOZ_ARRAY_LENGTH(sScanMode))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sScanMode[aIn];
return NS_OK;
}
nsresult
Convert(uint8_t aIn, BluetoothSspPairingVariant& aOut)
{
static const BluetoothSspPairingVariant sSspPairingVariant[] = {
CONVERT(0x00, SSP_VARIANT_PASSKEY_CONFIRMATION),
CONVERT(0x01, SSP_VARIANT_PASSKEY_ENTRY),
CONVERT(0x02, SSP_VARIANT_CONSENT),
CONVERT(0x03, SSP_VARIANT_PASSKEY_NOTIFICATION)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sSspPairingVariant))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sSspPairingVariant[aIn];
return NS_OK;
}
nsresult
Convert(uint8_t aIn, BluetoothStatus& aOut)
{
static const BluetoothStatus sStatus[] = {
CONVERT(0x00, STATUS_SUCCESS),
CONVERT(0x01, STATUS_FAIL),
CONVERT(0x02, STATUS_NOT_READY),
CONVERT(0x03, STATUS_NOMEM),
CONVERT(0x04, STATUS_BUSY),
CONVERT(0x05, STATUS_DONE),
CONVERT(0x06, STATUS_UNSUPPORTED),
CONVERT(0x07, STATUS_PARM_INVALID),
CONVERT(0x08, STATUS_UNHANDLED),
CONVERT(0x09, STATUS_AUTH_FAILURE),
CONVERT(0x0a, STATUS_RMT_DEV_DOWN)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sStatus))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sStatus[aIn];
return NS_OK;
}
nsresult
Convert(uint32_t aIn, int& aOut)
{
aOut = static_cast<int>(aIn);
return NS_OK;
}
nsresult
Convert(size_t aIn, uint16_t& aOut)
{
if (NS_WARN_IF(aIn >= (1ul << 16))) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = static_cast<uint16_t>(aIn);
return NS_OK;
}
nsresult
Convert(const nsAString& aIn, BluetoothAddress& aOut)
{
NS_ConvertUTF16toUTF8 bdAddressUTF8(aIn);
const char* str = bdAddressUTF8.get();
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(aOut.mAddr); ++i, ++str) {
aOut.mAddr[i] =
static_cast<uint8_t>(strtoul(str, const_cast<char**>(&str), 16));
}
return NS_OK;
}
nsresult
Convert(const nsAString& aIn, BluetoothPinCode& aOut)
{
if (NS_WARN_IF(aIn.Length() > MOZ_ARRAY_LENGTH(aOut.mPinCode))) {
return NS_ERROR_ILLEGAL_VALUE;
}
NS_ConvertUTF16toUTF8 pinCodeUTF8(aIn);
const char* str = pinCodeUTF8.get();
nsAString::size_type i;
// Fill pin into aOut
for (i = 0; i < aIn.Length(); ++i, ++str) {
aOut.mPinCode[i] = static_cast<uint8_t>(*str);
}
// Clear remaining bytes in aOut
size_t ntrailing = (MOZ_ARRAY_LENGTH(aOut.mPinCode) - aIn.Length()) *
sizeof(aOut.mPinCode[0]);
memset(aOut.mPinCode + aIn.Length(), 0, ntrailing);
aOut.mLength = aIn.Length();
return NS_OK;
}
nsresult
Convert(const nsAString& aIn, BluetoothPropertyType& aOut)
{
if (aIn.EqualsLiteral("Name")) {
aOut = PROPERTY_BDNAME;
} else if (aIn.EqualsLiteral("Discoverable")) {
aOut = PROPERTY_ADAPTER_SCAN_MODE;
} else if (aIn.EqualsLiteral("DiscoverableTimeout")) {
aOut = PROPERTY_ADAPTER_DISCOVERY_TIMEOUT;
} else {
BT_LOGR("Invalid property name: %s", NS_ConvertUTF16toUTF8(aIn).get());
aOut = static_cast<BluetoothPropertyType>(0); // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
return NS_OK;
}
nsresult
Convert(const nsAString& aIn, BluetoothServiceName& aOut)
{
NS_ConvertUTF16toUTF8 serviceNameUTF8(aIn);
const char* str = serviceNameUTF8.get();
size_t len = strlen(str);
if (NS_WARN_IF(len > sizeof(aOut.mName))) {
return NS_ERROR_ILLEGAL_VALUE;
}
memcpy(aOut.mName, str, len);
memset(aOut.mName + len, 0, sizeof(aOut.mName) - len);
return NS_OK;
}
nsresult
Convert(const nsAString& aIn, BluetoothSspPairingVariant& aOut)
{
if (aIn.EqualsLiteral("PasskeyConfirmation")) {
aOut = SSP_VARIANT_PASSKEY_CONFIRMATION;
} else if (aIn.EqualsLiteral("PasskeyEntry")) {
aOut = SSP_VARIANT_PASSKEY_ENTRY;
} else if (aIn.EqualsLiteral("Consent")) {
aOut = SSP_VARIANT_CONSENT;
} else if (aIn.EqualsLiteral("PasskeyNotification")) {
aOut = SSP_VARIANT_PASSKEY_NOTIFICATION;
} else {
BT_LOGR("Invalid SSP variant name: %s", NS_ConvertUTF16toUTF8(aIn).get());
aOut = SSP_VARIANT_PASSKEY_CONFIRMATION; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
return NS_OK;
}
nsresult
Convert(BluetoothAclState aIn, bool& aOut)
{
static const bool sBool[] = {
CONVERT(ACL_STATE_CONNECTED, true),
CONVERT(ACL_STATE_DISCONNECTED, false)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sBool))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sBool[aIn];
return NS_OK;
}
nsresult
Convert(const BluetoothAddress& aIn, nsAString& aOut)
{
char str[BLUETOOTH_ADDRESS_LENGTH + 1];
int res = snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x",
static_cast<int>(aIn.mAddr[0]),
static_cast<int>(aIn.mAddr[1]),
static_cast<int>(aIn.mAddr[2]),
static_cast<int>(aIn.mAddr[3]),
static_cast<int>(aIn.mAddr[4]),
static_cast<int>(aIn.mAddr[5]));
if (NS_WARN_IF(res < 0)) {
return NS_ERROR_ILLEGAL_VALUE;
} else if (NS_WARN_IF((size_t)res >= sizeof(str))) {
return NS_ERROR_OUT_OF_MEMORY; /* string buffer too small */
}
aOut = NS_ConvertUTF8toUTF16(str);
return NS_OK;
}
nsresult
Convert(BluetoothPropertyType aIn, uint8_t& aOut)
{
static const uint8_t sPropertyType[] = {
CONVERT(PROPERTY_UNKNOWN, 0x00),
CONVERT(PROPERTY_BDNAME, 0x01),
CONVERT(PROPERTY_BDADDR, 0x02),
CONVERT(PROPERTY_UUIDS, 0x03),
CONVERT(PROPERTY_CLASS_OF_DEVICE, 0x04),
CONVERT(PROPERTY_TYPE_OF_DEVICE, 0x05),
CONVERT(PROPERTY_SERVICE_RECORD, 0x06),
CONVERT(PROPERTY_ADAPTER_SCAN_MODE, 0x07),
CONVERT(PROPERTY_ADAPTER_BONDED_DEVICES, 0x08),
CONVERT(PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, 0x09),
CONVERT(PROPERTY_REMOTE_FRIENDLY_NAME, 0x0a),
CONVERT(PROPERTY_REMOTE_RSSI, 0x0b),
CONVERT(PROPERTY_REMOTE_VERSION_INFO, 0x0c),
CONVERT(PROPERTY_REMOTE_DEVICE_TIMESTAMP, 0xff)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sPropertyType))) {
aOut = 0x00; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sPropertyType[aIn];
return NS_OK;
}
nsresult
Convert(const BluetoothRemoteName& aIn, nsAString& aOut)
{
// We construct an nsCString here because the string
// returned from the PDU is not 0-terminated.
aOut = NS_ConvertUTF8toUTF16(
nsCString(reinterpret_cast<const char*>(aIn.mName), sizeof(aIn.mName)));
return NS_OK;
}
nsresult
Convert(BluetoothScanMode aIn, int32_t& aOut)
{
static const int32_t sScanMode[] = {
CONVERT(SCAN_MODE_NONE, 0x00),
CONVERT(SCAN_MODE_CONNECTABLE, 0x01),
CONVERT(SCAN_MODE_CONNECTABLE_DISCOVERABLE, 0x02)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sScanMode))) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sScanMode[aIn];
return NS_OK;
}
nsresult
Convert(BluetoothSspPairingVariant aIn, uint8_t& aOut)
{
static const uint8_t sValue[] = {
CONVERT(SSP_VARIANT_PASSKEY_CONFIRMATION, 0x00),
CONVERT(SSP_VARIANT_PASSKEY_ENTRY, 0x01),
CONVERT(SSP_VARIANT_CONSENT, 0x02),
CONVERT(SSP_VARIANT_PASSKEY_NOTIFICATION, 0x03)
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sValue))) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sValue[aIn];
return NS_OK;
}
nsresult
Convert(BluetoothSspPairingVariant aIn, nsAString& aOut)
{
static const char* const sString[] = {
CONVERT(SSP_VARIANT_PASSKEY_CONFIRMATION, "PasskeyConfirmation"),
CONVERT(SSP_VARIANT_PASSKEY_ENTRY, "PasskeyEntry"),
CONVERT(SSP_VARIANT_CONSENT, "Consent"),
CONVERT(SSP_VARIANT_PASSKEY_NOTIFICATION, "PasskeyNotification")
};
if (NS_WARN_IF(aIn >= MOZ_ARRAY_LENGTH(sString))) {
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = NS_ConvertUTF8toUTF16(sString[aIn]);
return NS_OK;
}
/* |ConvertArray| is a helper for converting arrays. Pass an
* instance of this structure as the first argument to |Convert|
* to convert an array. The output type has to support the array
* subscript operator.
*/
template <typename T>
struct ConvertArray
{
ConvertArray(const T* aData, unsigned long aLength)
: mData(aData)
, mLength(aLength)
{ }
const T* mData;
unsigned long mLength;
};
/* This implementation of |Convert| converts the elements of an
* array one-by-one. The result data structures must have enough
* memory allocated.
*/
template<typename Tin, typename Tout>
inline nsresult
Convert(const ConvertArray<Tin>& aIn, Tout& aOut)
{
for (unsigned long i = 0; i < aIn.mLength; ++i) {
nsresult rv = Convert(aIn.mData[i], aOut[i]);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
//
// Packing
//
nsresult
PackPDU(bool aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackConversion<bool, uint8_t>(aIn), aPDU);
}
nsresult
PackPDU(const BluetoothAddress& aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackArray<uint8_t>(aIn.mAddr, sizeof(aIn.mAddr)), aPDU);
}
nsresult
PackPDU(const BluetoothConfigurationParameter& aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(aIn.mType, aIn.mLength,
PackArray<uint8_t>(aIn.mValue.get(), aIn.mLength), aPDU);
}
nsresult
PackPDU(const BluetoothDaemonPDUHeader& aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(aIn.mService, aIn.mOpcode, aIn.mLength, aPDU);
}
nsresult
PackPDU(const BluetoothNamedValue& aIn, BluetoothDaemonPDU& aPDU)
{
nsresult rv = PackPDU(
PackConversion<nsString, BluetoothPropertyType>(aIn.name()), aPDU);
if (NS_FAILED(rv)) {
return rv;
}
if (aIn.value().type() == BluetoothValue::Tuint32_t) {
// Set discoverable timeout
rv = PackPDU(static_cast<uint16_t>(sizeof(uint32_t)),
aIn.value().get_uint32_t(), aPDU);
} else if (aIn.value().type() == BluetoothValue::TnsString) {
// Set name
const nsCString value =
NS_ConvertUTF16toUTF8(aIn.value().get_nsString());
rv = PackPDU(PackConversion<size_t, uint16_t>(value.Length()),
PackArray<uint8_t>(
reinterpret_cast<const uint8_t*>(value.get()),
value.Length()),
aPDU);
} else if (aIn.value().type() == BluetoothValue::Tbool) {
// Set scan mode
bool value = aIn.value().get_bool();
rv = PackPDU(static_cast<uint16_t>(sizeof(int32_t)),
PackConversion<bool, BluetoothScanMode>(value), aPDU);
} else {
BT_LOGR("Invalid property value type");
rv = NS_ERROR_ILLEGAL_VALUE;
}
return rv;
}
nsresult
PackPDU(const BluetoothPinCode& aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(aIn.mLength,
PackArray<uint8_t>(aIn.mPinCode, sizeof(aIn.mPinCode)),
aPDU);
}
nsresult
PackPDU(BluetoothPropertyType aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackConversion<BluetoothPropertyType, uint8_t>(aIn), aPDU);
}
nsresult
PackPDU(BluetoothSspPairingVariant aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackConversion<BluetoothSspPairingVariant, uint8_t>(aIn),
aPDU);
}
nsresult
PackPDU(BluetoothScanMode aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackConversion<BluetoothScanMode, int32_t>(aIn), aPDU);
}
nsresult
PackPDU(const BluetoothServiceName& aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackArray<uint8_t>(aIn.mName, sizeof(aIn.mName)), aPDU);
}
nsresult
PackPDU(BluetoothSocketType aIn, BluetoothDaemonPDU& aPDU)
{
return PackPDU(PackConversion<BluetoothSocketType, uint8_t>(aIn), aPDU);
}
//
// Unpacking
//
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, bool& aOut)
{
return UnpackPDU(aPDU, UnpackConversion<uint8_t, bool>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothAclState& aOut)
{
return UnpackPDU(aPDU, UnpackConversion<uint8_t, BluetoothAclState>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothBondState& aOut)
{
return UnpackPDU(aPDU, UnpackConversion<uint8_t, BluetoothBondState>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothDeviceType& aOut)
{
return UnpackPDU(
aPDU, UnpackConversion<int32_t, BluetoothDeviceType>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothProperty& aOut)
{
nsresult rv = UnpackPDU(aPDU, aOut.mType);
if (NS_FAILED(rv)) {
return rv;
}
uint16_t len;
rv = UnpackPDU(aPDU, len);
if (NS_FAILED(rv)) {
return rv;
}
switch (aOut.mType) {
case PROPERTY_BDNAME:
/* fall through */
case PROPERTY_REMOTE_FRIENDLY_NAME: {
const uint8_t* data = aPDU.Consume(len);
if (NS_WARN_IF(!data)) {
return NS_ERROR_ILLEGAL_VALUE;
}
// We construct an nsCString here because the string
// returned from the PDU is not 0-terminated.
aOut.mString = NS_ConvertUTF8toUTF16(
nsCString(reinterpret_cast<const char*>(data), len));
}
break;
case PROPERTY_BDADDR:
rv = UnpackPDU<BluetoothAddress>(
aPDU, UnpackConversion<BluetoothAddress, nsAString>(aOut.mString));
break;
case PROPERTY_UUIDS: {
size_t numUuids = len / MAX_UUID_SIZE;
aOut.mUuidArray.SetLength(numUuids);
rv = UnpackPDU(aPDU, aOut.mUuidArray);
}
break;
case PROPERTY_CLASS_OF_DEVICE:
/* fall through */
case PROPERTY_ADAPTER_DISCOVERY_TIMEOUT:
rv = UnpackPDU(aPDU, aOut.mUint32);
break;
case PROPERTY_TYPE_OF_DEVICE:
rv = UnpackPDU(aPDU, aOut.mDeviceType);
break;
case PROPERTY_SERVICE_RECORD:
rv = UnpackPDU(aPDU, aOut.mServiceRecord);
break;
case PROPERTY_ADAPTER_SCAN_MODE:
rv = UnpackPDU(aPDU, aOut.mScanMode);
break;
case PROPERTY_ADAPTER_BONDED_DEVICES: {
/* unpack addresses */
size_t numAddresses = len / BLUETOOTH_ADDRESS_BYTES;
nsAutoArrayPtr<BluetoothAddress> addresses;
UnpackArray<BluetoothAddress> addressArray(addresses, numAddresses);
rv = UnpackPDU(aPDU, addressArray);
if (NS_FAILED(rv)) {
return rv;
}
/* convert addresses to strings */
aOut.mStringArray.SetLength(numAddresses);
ConvertArray<BluetoothAddress> convertArray(addressArray.mData,
addressArray.mLength);
rv = Convert(convertArray, aOut.mStringArray);
}
break;
case PROPERTY_REMOTE_RSSI: {
int8_t rssi;
rv = UnpackPDU(aPDU, rssi);
aOut.mInt32 = rssi;
}
break;
case PROPERTY_REMOTE_VERSION_INFO:
rv = UnpackPDU(aPDU, aOut.mRemoteInfo);
break;
case PROPERTY_REMOTE_DEVICE_TIMESTAMP:
/* nothing to do */
break;
default:
break;
}
return rv;
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothPropertyType& aOut)
{
return UnpackPDU(
aPDU, UnpackConversion<uint8_t, BluetoothPropertyType>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothRemoteInfo& aOut)
{
nsresult rv = UnpackPDU(aPDU,
UnpackConversion<uint32_t, int>(aOut.mVerMajor));
if (NS_FAILED(rv)) {
return rv;
}
rv = UnpackPDU(aPDU, UnpackConversion<uint32_t, int>(aOut.mVerMinor));
if (NS_FAILED(rv)) {
return rv;
}
return UnpackPDU(aPDU, UnpackConversion<uint32_t, int>(aOut.mManufacturer));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothScanMode& aOut)
{
return UnpackPDU(aPDU, UnpackConversion<int32_t, BluetoothScanMode>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothServiceRecord& aOut)
{
/* unpack UUID */
nsresult rv = UnpackPDU(aPDU, aOut.mUuid);
if (NS_FAILED(rv)) {
return rv;
}
/* unpack channel */
rv = UnpackPDU(aPDU, aOut.mChannel);
if (NS_FAILED(rv)) {
return rv;
}
/* unpack name */
return aPDU.Read(aOut.mName, sizeof(aOut.mName));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothSspPairingVariant& aOut)
{
return UnpackPDU(
aPDU, UnpackConversion<uint8_t, BluetoothSspPairingVariant>(aOut));
}
nsresult
UnpackPDU(BluetoothDaemonPDU& aPDU, BluetoothStatus& aOut)
{
return UnpackPDU(aPDU, UnpackConversion<uint8_t, BluetoothStatus>(aOut));
}
END_BLUETOOTH_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluedroid_bluetoothdaemoninterface_h__
#define mozilla_dom_bluetooth_bluedroid_bluetoothdaemoninterface_h__
#include "BluetoothInterface.h"
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothDaemonChannel;
class BluetoothDaemonProtocol;
class BluetoothDaemonSocketInterface;
class BluetoothDaemonInterface MOZ_FINAL : public BluetoothInterface
{
public:
class CleanupResultHandler;
class InitResultHandler;
friend class BluetoothDaemonChannel;
friend class CleanupResultHandler;
friend class InitResultHandler;
static BluetoothDaemonInterface* GetInstance();
void Init(BluetoothNotificationHandler* aNotificationHandler,
BluetoothResultHandler* aRes);
void Cleanup(BluetoothResultHandler* aRes);
void Enable(BluetoothResultHandler* aRes);
void Disable(BluetoothResultHandler* aRes);
/* Adapter Properties */
void GetAdapterProperties(BluetoothResultHandler* aRes);
void GetAdapterProperty(const nsAString& aName,
BluetoothResultHandler* aRes);
void SetAdapterProperty(const BluetoothNamedValue& aProperty,
BluetoothResultHandler* aRes);
/* Remote Device Properties */
void GetRemoteDeviceProperties(const nsAString& aRemoteAddr,
BluetoothResultHandler* aRes);
void GetRemoteDeviceProperty(const nsAString& aRemoteAddr,
const nsAString& aName,
BluetoothResultHandler* aRes);
void SetRemoteDeviceProperty(const nsAString& aRemoteAddr,
const BluetoothNamedValue& aProperty,
BluetoothResultHandler* aRes);
/* Remote Services */
void GetRemoteServiceRecord(const nsAString& aRemoteAddr,
const uint8_t aUuid[16],
BluetoothResultHandler* aRes);
void GetRemoteServices(const nsAString& aRemoteAddr,
BluetoothResultHandler* aRes);
/* Discovery */
void StartDiscovery(BluetoothResultHandler* aRes);
void CancelDiscovery(BluetoothResultHandler* aRes);
/* Bonds */
void CreateBond(const nsAString& aBdAddr, BluetoothResultHandler* aRes);
void RemoveBond(const nsAString& aBdAddr, BluetoothResultHandler* aRes);
void CancelBond(const nsAString& aBdAddr, BluetoothResultHandler* aRes);
/* Authentication */
void PinReply(const nsAString& aBdAddr, bool aAccept,
const nsAString& aPinCode,
BluetoothResultHandler* aRes);
void SspReply(const nsAString& aBdAddr, const nsAString& aVariant,
bool aAccept, uint32_t aPasskey,
BluetoothResultHandler* aRes);
/* DUT Mode */
void DutModeConfigure(bool aEnable, BluetoothResultHandler* aRes);
void DutModeSend(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
BluetoothResultHandler* aRes);
/* LE Mode */
void LeTestMode(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
BluetoothResultHandler* aRes);
/* Profile Interfaces */
BluetoothSocketInterface* GetBluetoothSocketInterface() MOZ_OVERRIDE;
BluetoothHandsfreeInterface* GetBluetoothHandsfreeInterface() MOZ_OVERRIDE;
BluetoothA2dpInterface* GetBluetoothA2dpInterface() MOZ_OVERRIDE;
BluetoothAvrcpInterface* GetBluetoothAvrcpInterface() MOZ_OVERRIDE;
protected:
enum Channel {
CMD_CHANNEL,
NTF_CHANNEL
};
BluetoothDaemonInterface(BluetoothDaemonChannel* aCmdChannel,
BluetoothDaemonChannel* aNtfChannel,
BluetoothDaemonProtocol* aProtocol);
~BluetoothDaemonInterface();
void OnConnectSuccess(enum Channel aChannel);
void OnConnectError(enum Channel aChannel);
void OnDisconnect(enum Channel aChannel);
private:
void DispatchError(BluetoothResultHandler* aRes, BluetoothStatus aStatus);
nsAutoPtr<BluetoothDaemonChannel> mCmdChannel;
nsAutoPtr<BluetoothDaemonChannel> mNtfChannel;
nsAutoPtr<BluetoothDaemonProtocol> mProtocol;
nsTArray<nsRefPtr<BluetoothResultHandler> > mResultHandlerQ;
nsAutoPtr<BluetoothDaemonSocketInterface> mSocketInterface;
};
END_BLUETOOTH_NAMESPACE
#endif

View File

@ -0,0 +1,32 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothDaemonSetupInterface.h"
BEGIN_BLUETOOTH_NAMESPACE
BluetoothSetupResultHandler::~BluetoothSetupResultHandler()
{ }
void
BluetoothSetupResultHandler::OnError(BluetoothStatus aStatus)
{
BT_WARNING("Received error code %d", (int)aStatus);
}
void
BluetoothSetupResultHandler::RegisterModule()
{ }
void
BluetoothSetupResultHandler::UnregisterModule()
{ }
void
BluetoothSetupResultHandler::Configuration()
{ }
END_BLUETOOTH_NAMESPACE

View File

@ -0,0 +1,29 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluedroid_bluetoothdaemonsetupinterface_h__
#define mozilla_dom_bluetooth_bluedroid_bluetoothdaemonsetupinterface_h__
#include "BluetoothCommon.h"
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothSetupResultHandler
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothSetupResultHandler)
virtual ~BluetoothSetupResultHandler();
virtual void OnError(BluetoothStatus aStatus);
virtual void RegisterModule();
virtual void UnregisterModule();
virtual void Configuration();
};
END_BLUETOOTH_NAMESPACE
#endif

View File

@ -0,0 +1,326 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothDaemonSocketInterface.h"
#include "BluetoothSocketMessageWatcher.h"
#include "nsXULAppAPI.h"
#include "mozilla/unused.h"
BEGIN_BLUETOOTH_NAMESPACE
//
// Socket module
//
// Commands
//
nsresult
BluetoothDaemonSocketModule::ListenCmd(BluetoothSocketType aType,
const nsAString& aServiceName,
const uint8_t aServiceUuid[16],
int aChannel, bool aEncrypt,
bool aAuth,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoPtr<BluetoothDaemonPDU> pdu(new BluetoothDaemonPDU(0x02, 0x01, 0));
nsresult rv = PackPDU(
aType,
PackConversion<nsAString, BluetoothServiceName>(aServiceName),
PackArray<uint8_t>(aServiceUuid, 16),
PackConversion<int, uint16_t>(aChannel),
SocketFlags(aEncrypt, aAuth), *pdu);
if (NS_FAILED(rv)) {
return rv;
}
rv = Send(pdu, aRes);
if (NS_FAILED(rv)) {
return rv;
}
unused << pdu.forget();
return rv;
}
nsresult
BluetoothDaemonSocketModule::ConnectCmd(const nsAString& aBdAddr,
BluetoothSocketType aType,
const uint8_t aUuid[16],
int aChannel, bool aEncrypt,
bool aAuth,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoPtr<BluetoothDaemonPDU> pdu(new BluetoothDaemonPDU(0x02, 0x02, 0));
nsresult rv = PackPDU(
PackConversion<nsAString, BluetoothAddress>(aBdAddr),
aType,
PackArray<uint8_t>(aUuid, 16),
PackConversion<int, int16_t>(aChannel),
SocketFlags(aEncrypt, aAuth), *pdu);
if (NS_FAILED(rv)) {
return rv;
}
rv = Send(pdu, aRes);
if (NS_FAILED(rv)) {
return rv;
}
unused << pdu.forget();
return rv;
}
/* |DeleteTask| deletes a class instance on the I/O thread
*/
template <typename T>
class DeleteTask MOZ_FINAL : public Task
{
public:
DeleteTask(T* aPtr)
: mPtr(aPtr)
{ }
void Run() MOZ_OVERRIDE
{
mPtr = nullptr;
}
private:
nsAutoPtr<T> mPtr;
};
/* |AcceptWatcher| specializes SocketMessageWatcher for Accept
* operations by reading the socket messages from Bluedroid and
* forwarding the received client socket to the resource handler.
* The first message is received immediately. When there's a new
* connection, Bluedroid sends the 2nd message with the socket
* info and socket file descriptor.
*/
class BluetoothDaemonSocketModule::AcceptWatcher MOZ_FINAL
: public SocketMessageWatcher
{
public:
AcceptWatcher(int aFd, BluetoothSocketResultHandler* aRes)
: SocketMessageWatcher(aFd, aRes)
{ }
void Proceed(BluetoothStatus aStatus) MOZ_OVERRIDE
{
if (aStatus == STATUS_SUCCESS) {
IntStringIntResultRunnable::Dispatch(
GetResultHandler(), &BluetoothSocketResultHandler::Accept,
GetClientFd(), GetBdAddress(), GetConnectionStatus());
} else {
ErrorRunnable::Dispatch(GetResultHandler(),
&BluetoothSocketResultHandler::OnError,
aStatus);
}
MessageLoopForIO::current()->PostTask(
FROM_HERE, new DeleteTask<AcceptWatcher>(this));
}
};
nsresult
BluetoothDaemonSocketModule::AcceptCmd(int aFd,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
/* receive Bluedroid's socket-setup messages and client fd */
Task* t = new SocketMessageWatcherTask(new AcceptWatcher(aFd, aRes));
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
return NS_OK;
}
nsresult
BluetoothDaemonSocketModule::CloseCmd(BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
/* stop the watcher corresponding to |aRes| */
Task* t = new DeleteSocketMessageWatcherTask(aRes);
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
return NS_OK;
}
void
BluetoothDaemonSocketModule::HandleSvc(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
void* aUserData)
{
static void (BluetoothDaemonSocketModule::* const HandleRsp[])(
const BluetoothDaemonPDUHeader&,
BluetoothDaemonPDU&,
BluetoothSocketResultHandler*) = {
INIT_ARRAY_AT(0x00, &BluetoothDaemonSocketModule::ErrorRsp),
INIT_ARRAY_AT(0x01, &BluetoothDaemonSocketModule::ListenRsp),
INIT_ARRAY_AT(0x02, &BluetoothDaemonSocketModule::ConnectRsp),
};
if (NS_WARN_IF(MOZ_ARRAY_LENGTH(HandleRsp) <= aHeader.mOpcode) ||
NS_WARN_IF(!HandleRsp[aHeader.mOpcode])) {
return;
}
nsRefPtr<BluetoothSocketResultHandler> res =
already_AddRefed<BluetoothSocketResultHandler>(
static_cast<BluetoothSocketResultHandler*>(aUserData));
if (!res) {
return; // Return early if no result handler has been set
}
(this->*(HandleRsp[aHeader.mOpcode]))(aHeader, aPDU, res);
}
nsresult
BluetoothDaemonSocketModule::Send(BluetoothDaemonPDU* aPDU,
BluetoothSocketResultHandler* aRes)
{
aRes->AddRef(); // Keep reference for response
return Send(aPDU, static_cast<void*>(aRes));
}
uint8_t
BluetoothDaemonSocketModule::SocketFlags(bool aEncrypt, bool aAuth)
{
return (0x01 * aEncrypt) | (0x02 * aAuth);
}
// Responses
//
void
BluetoothDaemonSocketModule::ErrorRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes)
{
ErrorRunnable::Dispatch<0x02, 0x00>(
aRes, &BluetoothSocketResultHandler::OnError, aPDU);
}
void
BluetoothDaemonSocketModule::ListenRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes)
{
IntResultRunnable::Dispatch<0x02, 0x01>(
aRes, &BluetoothSocketResultHandler::Listen, aPDU);
}
/* |ConnectWatcher| specializes SocketMessageWatcher for
* connect operations by reading the socket messages from
* Bluedroid and forwarding the connected socket to the
* resource handler.
*/
class BluetoothDaemonSocketModule::ConnectWatcher MOZ_FINAL
: public SocketMessageWatcher
{
public:
ConnectWatcher(int aFd, BluetoothSocketResultHandler* aRes)
: SocketMessageWatcher(aFd, aRes)
{ }
void Proceed(BluetoothStatus aStatus) MOZ_OVERRIDE
{
if (aStatus == STATUS_SUCCESS) {
IntStringIntResultRunnable::Dispatch(
GetResultHandler(), &BluetoothSocketResultHandler::Connect,
GetFd(), GetBdAddress(), GetConnectionStatus());
} else {
ErrorRunnable::Dispatch(GetResultHandler(),
&BluetoothSocketResultHandler::OnError,
aStatus);
}
MessageLoopForIO::current()->PostTask(
FROM_HERE, new DeleteTask<ConnectWatcher>(this));
}
};
void
BluetoothDaemonSocketModule::ConnectRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes)
{
/* the file descriptor is attached in the PDU's ancillary data */
int fd = aPDU.AcquireFd();
if (fd < 0) {
ErrorRunnable::Dispatch(
aRes, &BluetoothSocketResultHandler::OnError, STATUS_FAIL);
return;
}
/* receive Bluedroid's socket-setup messages */
Task* t = new SocketMessageWatcherTask(new ConnectWatcher(fd, aRes));
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
}
//
// Socket interface
//
BluetoothDaemonSocketInterface::BluetoothDaemonSocketInterface(
BluetoothDaemonSocketModule* aModule)
: mModule(aModule)
{
MOZ_ASSERT(mModule);
}
BluetoothDaemonSocketInterface::~BluetoothDaemonSocketInterface()
{ }
void
BluetoothDaemonSocketInterface::Listen(BluetoothSocketType aType,
const nsAString& aServiceName,
const uint8_t aServiceUuid[16],
int aChannel, bool aEncrypt,
bool aAuth,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(mModule);
mModule->ListenCmd(aType, aServiceName, aServiceUuid, aChannel,
aEncrypt, aAuth, aRes);
}
void
BluetoothDaemonSocketInterface::Connect(const nsAString& aBdAddr,
BluetoothSocketType aType,
const uint8_t aUuid[16],
int aChannel, bool aEncrypt,
bool aAuth,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(mModule);
mModule->ConnectCmd(aBdAddr, aType, aUuid, aChannel, aEncrypt, aAuth, aRes);
}
void
BluetoothDaemonSocketInterface::Accept(int aFd,
BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(mModule);
mModule->AcceptCmd(aFd, aRes);
}
void
BluetoothDaemonSocketInterface::Close(BluetoothSocketResultHandler* aRes)
{
MOZ_ASSERT(mModule);
mModule->CloseCmd(aRes);
}
END_BLUETOOTH_NAMESPACE

View File

@ -0,0 +1,121 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluedroid_bluetoothdaemonsocketinterface_h__
#define mozilla_dom_bluetooth_bluedroid_bluetoothdaemonsocketinterface_h__
#include "BluetoothDaemonHelpers.h"
#include "BluetoothInterface.h"
BEGIN_BLUETOOTH_NAMESPACE
using namespace mozilla::ipc;
class BlutoothDaemonInterface;
class BluetoothDaemonSocketModule
{
public:
virtual nsresult Send(BluetoothDaemonPDU* aPDU, void* aUserData) = 0;
// Commands
//
nsresult ListenCmd(BluetoothSocketType aType,
const nsAString& aServiceName,
const uint8_t aServiceUuid[16],
int aChannel, bool aEncrypt, bool aAuth,
BluetoothSocketResultHandler* aRes);
nsresult ConnectCmd(const nsAString& aBdAddr,
BluetoothSocketType aType,
const uint8_t aUuid[16],
int aChannel, bool aEncrypt, bool aAuth,
BluetoothSocketResultHandler* aRes);
nsresult AcceptCmd(int aFd, BluetoothSocketResultHandler* aRes);
nsresult CloseCmd(BluetoothSocketResultHandler* aRes);
protected:
void HandleSvc(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU, void* aUserData);
nsresult Send(BluetoothDaemonPDU* aPDU, BluetoothSocketResultHandler* aRes);
private:
class AcceptWatcher;
class ConnectWatcher;
uint8_t SocketFlags(bool aEncrypt, bool aAuth);
// Responses
//
typedef
BluetoothDaemonInterfaceRunnable0<BluetoothSocketResultHandler, void>
ResultRunnable;
typedef
BluetoothDaemonInterfaceRunnable1<BluetoothSocketResultHandler, void,
int, int>
IntResultRunnable;
typedef
BluetoothDaemonInterfaceRunnable1<BluetoothSocketResultHandler, void,
BluetoothStatus, BluetoothStatus>
ErrorRunnable;
typedef
BluetoothDaemonInterfaceRunnable3<BluetoothSocketResultHandler, void,
int, const nsString, int,
int, const nsAString_internal&, int>
IntStringIntResultRunnable;
void ErrorRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes);
void ListenRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes);
void ConnectRsp(const BluetoothDaemonPDUHeader& aHeader,
BluetoothDaemonPDU& aPDU,
BluetoothSocketResultHandler* aRes);
};
class BluetoothDaemonSocketInterface MOZ_FINAL
: public BluetoothSocketInterface
{
public:
BluetoothDaemonSocketInterface(BluetoothDaemonSocketModule* aModule);
~BluetoothDaemonSocketInterface();
void Listen(BluetoothSocketType aType,
const nsAString& aServiceName,
const uint8_t aServiceUuid[16],
int aChannel, bool aEncrypt, bool aAuth,
BluetoothSocketResultHandler* aRes);
void Connect(const nsAString& aBdAddr,
BluetoothSocketType aType,
const uint8_t aUuid[16],
int aChannel, bool aEncrypt, bool aAuth,
BluetoothSocketResultHandler* aRes);
void Accept(int aFd, BluetoothSocketResultHandler* aRes);
void Close(BluetoothSocketResultHandler* aRes);
private:
BluetoothDaemonSocketModule* mModule;
};
END_BLUETOOTH_NAMESPACE
#endif

View File

@ -48,6 +48,10 @@ if CONFIG['MOZ_B2G_BT']:
'bluedroid/BluetoothA2dpHALInterface.cpp',
'bluedroid/BluetoothA2dpManager.cpp',
'bluedroid/BluetoothAvrcpHALInterface.cpp',
'bluedroid/BluetoothDaemonHelpers.cpp',
'bluedroid/BluetoothDaemonInterface.cpp',
'bluedroid/BluetoothDaemonSetupInterface.cpp',
'bluedroid/BluetoothDaemonSocketInterface.cpp',
'bluedroid/BluetoothHALHelpers.cpp',
'bluedroid/BluetoothHALInterface.cpp',
'bluedroid/BluetoothHandsfreeHALInterface.cpp',
@ -78,6 +82,8 @@ if CONFIG['MOZ_B2G_BT']:
]
DEFINES['MOZ_B2G_BT_BLUEDROID'] = True
if CONFIG['MOZ_B2G_BT_DAEMON']:
DEFINES['MOZ_B2G_BT_DAEMON'] = True
elif CONFIG['MOZ_ENABLE_DBUS']:
CFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
CFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']

View File

@ -8,7 +8,6 @@
#include "mozilla/unused.h"
#include "nsPrintfCString.h"
#include "nsIWeakReferenceUtils.h"
#include "CameraRecorderProfiles.h"
#include "CameraCommon.h"
#include "nsGlobalWindow.h"
#include "DeviceStorageFileDescriptor.h"
@ -65,12 +64,6 @@ CameraControlImpl::~CameraControlImpl()
}
}
already_AddRefed<RecorderProfileManager>
CameraControlImpl::GetRecorderProfileManager()
{
return GetRecorderProfileManagerImpl();
}
void
CameraControlImpl::Shutdown()
{
@ -114,7 +107,7 @@ CameraControlImpl::OnConfigurationChange()
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
RwLockAutoEnterRead lock(mListenerLock);
DOM_CAMERA_LOGI("OnConfigurationChange : %d listeners\n", mListeners.Length());
DOM_CAMERA_LOGI("OnConfigurationChange : %zu listeners\n", mListeners.Length());
for (uint32_t i = 0; i < mListeners.Length(); ++i) {
CameraControlListener* l = mListeners[i];
@ -269,7 +262,7 @@ CameraControlImpl::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uin
// On Gonk, it is called from the camera driver's preview thread.
RwLockAutoEnterRead lock(mListenerLock);
DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %d preview frame listener(s)\n",
DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %zu preview frame listener(s)\n",
mListeners.Length());
bool consumed = false;

View File

@ -25,8 +25,6 @@ namespace layers {
class Image;
}
class RecorderProfileManager;
class CameraControlImpl : public ICameraControl
{
public:
@ -49,7 +47,6 @@ public:
virtual nsresult StopRecording() MOZ_OVERRIDE;
virtual nsresult ResumeContinuousFocus() MOZ_OVERRIDE;
already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
uint32_t GetCameraId() { return mCameraId; }
virtual void Shutdown() MOZ_OVERRIDE;
@ -128,8 +125,6 @@ protected:
virtual nsresult PushParametersImpl() = 0;
virtual nsresult PullParametersImpl() = 0;
virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() = 0;
void OnShutterInternal();
void OnClosedInternal();

View File

@ -34,8 +34,8 @@ public:
enum HardwareState
{
kHardwareOpen,
kHardwareClosed
kHardwareClosed,
kHardwareOpen
};
virtual void OnHardwareStateChange(HardwareState aState) { }

View File

@ -1,195 +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/. */
#include "CameraRecorderProfiles.h"
#include "jsapi.h"
#include "CameraCommon.h"
using namespace mozilla;
/**
* Video profile implementation.
*/
RecorderVideoProfile::RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: mCameraId(aCameraId)
, mQualityIndex(aQualityIndex)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
RecorderVideoProfile::~RecorderVideoProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
nsresult
RecorderVideoProfile::GetJsObject(JSContext* aCx, JSObject** aObject)
{
NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
const char* codec = GetCodecName();
NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
if (!JS_SetProperty(aCx, o, "codec", v)) {
return NS_ERROR_FAILURE;
}
if (mBitrate != -1) {
v = INT_TO_JSVAL(mBitrate);
if (!JS_SetProperty(aCx, o, "bitrate", v)) {
return NS_ERROR_FAILURE;
}
}
if (mFramerate != -1) {
v = INT_TO_JSVAL(mFramerate);
if (!JS_SetProperty(aCx, o, "framerate", v)) {
return NS_ERROR_FAILURE;
}
}
if (mWidth != -1) {
v = INT_TO_JSVAL(mWidth);
if (!JS_SetProperty(aCx, o, "width", v)) {
return NS_ERROR_FAILURE;
}
}
if (mHeight != -1) {
v = INT_TO_JSVAL(mHeight);
if (!JS_SetProperty(aCx, o, "height", v)) {
return NS_ERROR_FAILURE;
}
}
*aObject = o;
return NS_OK;
}
/**
* Audio profile implementation.
*/
RecorderAudioProfile::RecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: mCameraId(aCameraId)
, mQualityIndex(aQualityIndex)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
RecorderAudioProfile::~RecorderAudioProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
nsresult
RecorderAudioProfile::GetJsObject(JSContext* aCx, JSObject** aObject)
{
NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
const char* codec = GetCodecName();
NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
if (!JS_SetProperty(aCx, o, "codec", v)) {
return NS_ERROR_FAILURE;
}
if (mBitrate != -1) {
v = INT_TO_JSVAL(mBitrate);
if (!JS_SetProperty(aCx, o, "bitrate", v)) {
return NS_ERROR_FAILURE;
}
}
if (mSamplerate != -1) {
v = INT_TO_JSVAL(mSamplerate);
if (!JS_SetProperty(aCx, o, "samplerate", v)) {
return NS_ERROR_FAILURE;
}
}
if (mChannels != -1) {
v = INT_TO_JSVAL(mChannels);
if (!JS_SetProperty(aCx, o, "channels", v)) {
return NS_ERROR_FAILURE;
}
}
*aObject = o;
return NS_OK;
}
/**
* Recorder Profile
*/
RecorderProfile::RecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: mCameraId(aCameraId)
, mQualityIndex(aQualityIndex)
, mName(nullptr)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
RecorderProfile::~RecorderProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
/**
* Recorder profile manager implementation.
*/
RecorderProfileManager::RecorderProfileManager(uint32_t aCameraId)
: mCameraId(aCameraId)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
RecorderProfileManager::~RecorderProfileManager()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
nsresult
RecorderProfileManager::GetJsObject(JSContext* aCx, JSObject** aObject) const
{
NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
if (!o) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (uint32_t q = 0; q < GetMaxQualityIndex(); ++q) {
if (!IsSupported(q)) {
continue;
}
nsRefPtr<RecorderProfile> profile = Get(q);
if (!profile) {
return NS_ERROR_OUT_OF_MEMORY;
}
const char* profileName = profile->GetName();
if (!profileName) {
// don't allow anonymous recorder profiles
continue;
}
JS::Rooted<JSObject*> p(aCx);
nsresult rv = profile->GetJsObject(aCx, p.address());
NS_ENSURE_SUCCESS(rv, rv);
JS::Rooted<JS::Value> v(aCx, OBJECT_TO_JSVAL(p));
if (!JS_SetProperty(aCx, o, profileName, v)) {
return NS_ERROR_FAILURE;
}
}
*aObject = o;
return NS_OK;
}

View File

@ -1,274 +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/. */
#ifndef DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
#define DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
#include "nsISupportsImpl.h"
#include "nsMimeTypes.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "jsapi.h"
#include "CameraCommon.h"
namespace mozilla {
class CameraControlImpl;
class RecorderVideoProfile
{
public:
RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex);
virtual ~RecorderVideoProfile();
int GetBitrate() const { return mBitrate; }
int GetFramerate() const { return mFramerate; }
int GetWidth() const { return mWidth; }
int GetHeight() const { return mHeight; }
enum Codec {
H263,
H264,
MPEG4SP,
UNKNOWN
};
Codec GetCodec() const { return mCodec; }
const char* GetCodecName() const
{
switch (mCodec) {
case H263: return "h263";
case H264: return "h264";
case MPEG4SP: return "mpeg4sp";
default: return nullptr;
}
}
// Get a representation of this video profile that can be returned
// to JS, possibly as a child member of another object.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aObject' is null;
// - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
// - NS_ERROR_FAILURE if construction of the JS object fails.
nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
protected:
uint32_t mCameraId;
uint32_t mQualityIndex;
Codec mCodec;
int mBitrate;
int mFramerate;
int mWidth;
int mHeight;
};
class RecorderAudioProfile
{
public:
RecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex);
virtual ~RecorderAudioProfile();
int GetBitrate() const { return mBitrate; }
int GetSamplerate() const { return mSamplerate; }
int GetChannels() const { return mChannels; }
enum Codec {
AMRNB,
AMRWB,
AAC,
UNKNOWN
};
Codec GetCodec() const { return mCodec; }
const char* GetCodecName() const
{
switch (mCodec) {
case AMRNB: return "amrnb";
case AMRWB: return "amrwb";
case AAC: return "aac";
default: return nullptr;
}
}
// Get a representation of this audio profile that can be returned
// to JS, possibly as a child member of another object.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aObject' is null;
// - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
// - NS_ERROR_FAILURE if construction of the JS object fails.
nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
protected:
uint32_t mCameraId;
uint32_t mQualityIndex;
Codec mCodec;
int mBitrate;
int mSamplerate;
int mChannels;
};
class RecorderProfile
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfile)
RecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex);
virtual const RecorderVideoProfile* GetVideoProfile() const = 0;
virtual const RecorderAudioProfile* GetAudioProfile() const = 0;
const char* GetName() const { return mName; }
enum FileFormat {
THREE_GPP,
MPEG4,
UNKNOWN
};
FileFormat GetFileFormat() const { return mFileFormat; }
const char* GetFileFormatName() const
{
switch (mFileFormat) {
case THREE_GPP: return "3gp";
case MPEG4: return "mp4";
default: return nullptr;
}
}
const char* GetFileMimeType() const
{
switch (mFileFormat) {
case THREE_GPP: return VIDEO_3GPP;
case MPEG4: return VIDEO_MP4;
default: return nullptr;
}
}
// Get a representation of this recorder profile that can be returned
// to JS, possibly as a child member of another object.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aObject' is null;
// - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
// - NS_ERROR_FAILURE if construction of the JS object fails.
virtual nsresult GetJsObject(JSContext* aCx, JSObject** aObject) = 0;
protected:
virtual ~RecorderProfile();
uint32_t mCameraId;
uint32_t mQualityIndex;
const char* mName;
FileFormat mFileFormat;
};
template <class Audio, class Video>
class RecorderProfileBase : public RecorderProfile
{
public:
RecorderProfileBase(uint32_t aCameraId, uint32_t aQualityIndex)
: RecorderProfile(aCameraId, aQualityIndex)
, mVideo(aCameraId, aQualityIndex)
, mAudio(aCameraId, aQualityIndex)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
virtual ~RecorderProfileBase()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
const RecorderVideoProfile* GetVideoProfile() const { return &mVideo; }
const RecorderAudioProfile* GetAudioProfile() const { return &mAudio; }
// Get a representation of this recorder profile that can be returned
// to JS, possibly as a child member of another object.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aObject' is null;
// - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
// - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
// - NS_ERROR_FAILURE if construction of the JS object fails.
nsresult
GetJsObject(JSContext* aCx, JSObject** aObject)
{
NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
const char* format = GetFileFormatName();
if (!format) {
// the profile must have a file format
return NS_ERROR_NOT_AVAILABLE;
}
JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
if (!o) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, format));
JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
if (!JS_SetProperty(aCx, o, "format", v)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSObject*> video(aCx);
nsresult rv = mVideo.GetJsObject(aCx, video.address());
NS_ENSURE_SUCCESS(rv, rv);
v = OBJECT_TO_JSVAL(video);
if (!JS_SetProperty(aCx, o, "video", v)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSObject*> audio(aCx);
rv = mAudio.GetJsObject(aCx, audio.address());
NS_ENSURE_SUCCESS(rv, rv);
v = OBJECT_TO_JSVAL(audio);
if (!JS_SetProperty(aCx, o, "audio", v)) {
return NS_ERROR_FAILURE;
}
*aObject = o;
return NS_OK;
}
protected:
Video mVideo;
Audio mAudio;
};
class RecorderProfileManager
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfileManager)
virtual bool IsSupported(uint32_t aQualityIndex) const { return true; }
virtual already_AddRefed<RecorderProfile> Get(uint32_t aQualityIndex) const = 0;
uint32_t GetMaxQualityIndex() const { return mMaxQualityIndex; }
// Get a representation of all supported recorder profiles that can be
// returned to JS.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aObject' is null;
// - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
// - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
// - NS_ERROR_FAILURE if construction of the JS object fails.
nsresult GetJsObject(JSContext* aCx, JSObject** aObject) const;
protected:
explicit RecorderProfileManager(uint32_t aCameraId);
virtual ~RecorderProfileManager();
uint32_t mCameraId;
uint32_t mMaxQualityIndex;
};
} // namespace mozilla
#endif // DOM_CAMERA_CAMERA_RECORDER_PROFILES_H

View File

@ -12,28 +12,202 @@
#include "Navigator.h"
#include "CameraCommon.h"
#include "ICameraControl.h"
#include "CameraRecorderProfiles.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(CameraCapabilities)
/**
* CameraRecorderVideoProfile
*/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderVideoProfile, mParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CameraCapabilities)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
tmp->mRecorderProfiles = JS::UndefinedValue();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderVideoProfile)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderVideoProfile)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CameraCapabilities)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderVideoProfile)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CameraCapabilities)
NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mRecorderProfiles)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
JSObject*
CameraRecorderVideoProfile::WrapObject(JSContext* aCx)
{
return CameraRecorderVideoProfileBinding::Wrap(aCx, this);
}
CameraRecorderVideoProfile::CameraRecorderVideoProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile::Video& aProfile)
: mParent(aParent)
, mCodec(aProfile.GetCodec())
, mBitrate(aProfile.GetBitsPerSecond())
, mFramerate(aProfile.GetFramesPerSecond())
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mSize.mWidth = aProfile.GetSize().width;
mSize.mHeight = aProfile.GetSize().height;
DOM_CAMERA_LOGI(" video: '%s' %ux%u bps=%u fps=%u\n",
NS_ConvertUTF16toUTF8(mCodec).get(), mSize.mWidth, mSize.mHeight, mBitrate, mFramerate);
}
CameraRecorderVideoProfile::~CameraRecorderVideoProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
/**
* CameraRecorderAudioProfile
*/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderAudioProfile, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderAudioProfile)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderAudioProfile)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderAudioProfile)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject*
CameraRecorderAudioProfile::WrapObject(JSContext* aCx)
{
return CameraRecorderAudioProfileBinding::Wrap(aCx, this);
}
CameraRecorderAudioProfile::CameraRecorderAudioProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile::Audio& aProfile)
: mParent(aParent)
, mCodec(aProfile.GetCodec())
, mBitrate(aProfile.GetBitsPerSecond())
, mSamplerate(aProfile.GetSamplesPerSecond())
, mChannels(aProfile.GetChannels())
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
DOM_CAMERA_LOGI(" audio: '%s' bps=%u samples/s=%u channels=%u\n",
NS_ConvertUTF16toUTF8(mCodec).get(), mBitrate, mSamplerate, mChannels);
}
CameraRecorderAudioProfile::~CameraRecorderAudioProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
/**
* CameraRecorderProfile
*/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderProfile, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderProfile)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderProfile)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderProfile)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject*
CameraRecorderProfile::WrapObject(JSContext* aCx)
{
return CameraRecorderProfileBinding::Wrap(aCx, this);
}
CameraRecorderProfile::CameraRecorderProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile& aProfile)
: mParent(aParent)
, mName(aProfile.GetName())
, mContainerFormat(aProfile.GetContainer())
, mMimeType(aProfile.GetMimeType())
, mVideo(new CameraRecorderVideoProfile(this, aProfile.GetVideo()))
, mAudio(new CameraRecorderAudioProfile(this, aProfile.GetAudio()))
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
DOM_CAMERA_LOGI("profile: '%s' container=%s mime-type=%s\n",
NS_ConvertUTF16toUTF8(mName).get(),
NS_ConvertUTF16toUTF8(mContainerFormat).get(),
NS_ConvertUTF16toUTF8(mMimeType).get());
}
CameraRecorderProfile::~CameraRecorderProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
/**
* CameraRecorderProfiles
*/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderProfiles, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderProfiles)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderProfiles)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderProfiles)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject*
CameraRecorderProfiles::WrapObject(JSContext* aCx)
{
return CameraRecorderProfilesBinding::Wrap(aCx, this);
}
CameraRecorderProfiles::CameraRecorderProfiles(nsISupports* aParent,
ICameraControl* aCameraControl)
: mParent(aParent)
, mCameraControl(aCameraControl)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
CameraRecorderProfiles::~CameraRecorderProfiles()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
void
CameraRecorderProfiles::GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, flags=0x%x\n",
__func__, __LINE__, this, aFlags);
nsresult rv = mCameraControl->GetRecorderProfiles(aNames);
if (NS_WARN_IF(NS_FAILED(rv))) {
aNames.Clear();
}
}
CameraRecorderProfile*
CameraRecorderProfiles::NamedGetter(const nsAString& aName, bool& aFound)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, name='%s'\n", __func__, __LINE__, this,
NS_ConvertUTF16toUTF8(aName).get());
CameraRecorderProfile* profile = mProfiles.GetWeak(aName, &aFound);
if (!aFound || !profile) {
nsRefPtr<ICameraControl::RecorderProfile> p = mCameraControl->GetProfileInfo(aName);
if (p) {
profile = new CameraRecorderProfile(this, *p);
mProfiles.Put(aName, profile);
aFound = true;
}
}
return profile;
}
bool
CameraRecorderProfiles::NameIsEnumerable(const nsAString& aName)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, name='%s' (always returns true)\n",
__func__, __LINE__, this, NS_ConvertUTF16toUTF8(aName).get());
return true;
}
/**
* CameraCapabilities
*/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraCapabilities, mWindow)
NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraCapabilities)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraCapabilities)
@ -50,20 +224,24 @@ CameraCapabilities::HasSupport(JSContext* aCx, JSObject* aGlobal)
return Navigator::HasCameraSupport(aCx, aGlobal);
}
CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow)
: mRecorderProfiles(JS::UndefinedValue())
CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow,
ICameraControl* aCameraControl)
: mMaxFocusAreas(0)
, mMaxMeteringAreas(0)
, mMaxDetectedFaces(0)
, mMinExposureCompensation(0.0)
, mMaxExposureCompensation(0.0)
, mExposureCompensationStep(0.0)
, mWindow(aWindow)
, mCameraControl(aCameraControl)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
MOZ_COUNT_CTOR(CameraCapabilities);
mozilla::HoldJSObjects(this);
}
CameraCapabilities::~CameraCapabilities()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mRecorderProfiles = JS::UndefinedValue();
mozilla::DropJSObjects(this);
MOZ_COUNT_DTOR(CameraCapabilities);
}
@ -82,13 +260,12 @@ CameraCapabilities::WrapObject(JSContext* aCx)
} while(0)
nsresult
CameraCapabilities::TranslateToDictionary(ICameraControl* aCameraControl,
uint32_t aKey, nsTArray<CameraSize>& aSizes)
CameraCapabilities::TranslateToDictionary(uint32_t aKey, nsTArray<CameraSize>& aSizes)
{
nsresult rv;
nsTArray<ICameraControl::Size> sizes;
rv = aCameraControl->Get(aKey, sizes);
rv = mCameraControl->Get(aKey, sizes);
if (NS_FAILED(rv)) {
return rv;
}
@ -104,205 +281,218 @@ CameraCapabilities::TranslateToDictionary(ICameraControl* aCameraControl,
return NS_OK;
}
nsresult
CameraCapabilities::Populate(ICameraControl* aCameraControl)
{
NS_ENSURE_TRUE(aCameraControl, NS_ERROR_INVALID_ARG);
nsresult rv;
rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, mPreviewSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PICTURESIZES, mPictureSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, mThumbnailSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_VIDEOSIZES, mVideoSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_VIDEOSIZES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_PICTUREFORMATS, mFileFormats);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_WHITEBALANCES, mWhiteBalanceModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_SCENEMODES, mSceneModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_SCENEMODES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EFFECTS, mEffects);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES, mFlashModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES, mFocusModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ISOMODES, mIsoModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ISOMODES);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
int32_t areas;
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
mMaxFocusAreas = areas < 0 ? 0 : areas;
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
mMaxMeteringAreas = areas < 0 ? 0 : areas;
int32_t faces;
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES, faces);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES);
mMaxDetectedFaces = faces < 0 ? 0 : faces;
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mMinExposureCompensation);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION, mMaxExposureCompensation);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
mRecorderProfileManager = aCameraControl->GetRecorderProfileManager();
if (!mRecorderProfileManager) {
DOM_CAMERA_LOGW("Unable to get recorder profile manager\n");
} else {
AutoJSContext js;
JS::Rooted<JSObject*> o(js);
nsresult rv = mRecorderProfileManager->GetJsObject(js, o.address());
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to JS-objectify profile manager (0x%x)\n", rv);
} else {
mRecorderProfiles = JS::ObjectValue(*o);
}
}
// For now, always return success, since the presence or absence of capabilities
// indicates whether or not they are supported.
return NS_OK;
}
void
CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval) const
CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval)
{
if (mPreviewSizes.Length() == 0) {
nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
mPreviewSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
}
retval = mPreviewSizes;
}
void
CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval) const
CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval)
{
if (mPictureSizes.Length() == 0) {
nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_PICTURESIZES,
mPictureSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
}
retval = mPictureSizes;
}
void
CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval) const
CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval)
{
if (mThumbnailSizes.Length() == 0) {
nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES,
mThumbnailSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
}
retval = mThumbnailSizes;
}
void
CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval) const
CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval)
{
if (mVideoSizes.Length() == 0) {
nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
mVideoSizes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_VIDEOSIZES);
}
retval = mVideoSizes;
}
void
CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval) const
CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval)
{
if (mFileFormats.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
mFileFormats);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
}
retval = mFileFormats;
}
void
CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval) const
CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval)
{
if (mWhiteBalanceModes.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
mWhiteBalanceModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
}
retval = mWhiteBalanceModes;
}
void
CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval) const
CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval)
{
if (mSceneModes.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_SCENEMODES,
mSceneModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_SCENEMODES);
}
retval = mSceneModes;
}
void
CameraCapabilities::GetEffects(nsTArray<nsString>& retval) const
CameraCapabilities::GetEffects(nsTArray<nsString>& retval)
{
if (mEffects.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_EFFECTS,
mEffects);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
}
retval = mEffects;
}
void
CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval) const
CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval)
{
if (mFlashModes.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES,
mFlashModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
}
retval = mFlashModes;
}
void
CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval) const
CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval)
{
if (mFocusModes.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES,
mFocusModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
}
retval = mFocusModes;
}
void
CameraCapabilities::GetZoomRatios(nsTArray<double>& retval) const
CameraCapabilities::GetZoomRatios(nsTArray<double>& retval)
{
if (mZoomRatios.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS,
mZoomRatios);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
}
retval = mZoomRatios;
}
uint32_t
CameraCapabilities::MaxFocusAreas() const
CameraCapabilities::MaxFocusAreas()
{
if (mMaxFocusAreas == 0) {
int32_t areas;
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS,
areas);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
mMaxFocusAreas = areas < 0 ? 0 : areas;
}
return mMaxFocusAreas;
}
uint32_t
CameraCapabilities::MaxMeteringAreas() const
CameraCapabilities::MaxMeteringAreas()
{
if (mMaxMeteringAreas == 0) {
int32_t areas;
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
areas);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
mMaxMeteringAreas = areas < 0 ? 0 : areas;
}
return mMaxMeteringAreas;
}
uint32_t
CameraCapabilities::MaxDetectedFaces() const
CameraCapabilities::MaxDetectedFaces()
{
if (mMaxDetectedFaces == 0) {
int32_t faces;
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES,
faces);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES);
mMaxDetectedFaces = faces < 0 ? 0 : faces;
}
return mMaxDetectedFaces;
}
double
CameraCapabilities::MinExposureCompensation() const
CameraCapabilities::MinExposureCompensation()
{
if (mMinExposureCompensation == 0.0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
mMinExposureCompensation);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
}
return mMinExposureCompensation;
}
double
CameraCapabilities::MaxExposureCompensation() const
CameraCapabilities::MaxExposureCompensation()
{
if (mMaxExposureCompensation == 0.0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
mMaxExposureCompensation);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
}
return mMaxExposureCompensation;
}
double
CameraCapabilities::ExposureCompensationStep() const
CameraCapabilities::ExposureCompensationStep()
{
if (mExposureCompensationStep == 0.0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
mExposureCompensationStep);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
}
return mExposureCompensationStep;
}
void
CameraCapabilities::GetRecorderProfiles(JSContext* aCx,
JS::MutableHandle<JS::Value> aRetval) const
CameraRecorderProfiles*
CameraCapabilities::RecorderProfiles()
{
JS::ExposeValueToActiveJS(mRecorderProfiles);
aRetval.set(mRecorderProfiles);
nsRefPtr<CameraRecorderProfiles> profiles = mRecorderProfiles;
if (!mRecorderProfiles) {
profiles = new CameraRecorderProfiles(this, mCameraControl);
mRecorderProfiles = profiles;
}
return profiles;
}
void
CameraCapabilities::GetIsoModes(nsTArray<nsString>& retval) const
CameraCapabilities::GetIsoModes(nsTArray<nsString>& retval)
{
if (mIsoModes.Length() == 0) {
nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_ISOMODES,
mIsoModes);
LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ISOMODES);
}
retval = mIsoModes;
}

View File

@ -9,23 +9,173 @@
#include "nsString.h"
#include "nsAutoPtr.h"
#include "base/basictypes.h"
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/CameraManagerBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "nsPIDOMWindow.h"
#include "nsHashKeys.h"
#include "nsRefPtrHashtable.h"
#include "nsDataHashtable.h"
#include "ICameraControl.h"
struct JSContext;
class nsPIDOMWindow;
namespace mozilla {
class ICameraControl;
class RecorderProfileManager;
namespace dom {
/**
* CameraRecorderVideoProfile
*/
class CameraRecorderVideoProfile MOZ_FINAL : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderVideoProfile)
explicit CameraRecorderVideoProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile::Video& aProfile);
nsISupports* GetParentObject() const { return mParent; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
uint32_t BitsPerSecond() const { return mBitrate; }
uint32_t FramesPerSecond() const { return mFramerate; }
void GetCodec(nsAString& aCodec) const { aCodec = mCodec; }
void GetSize(dom::CameraSize& aSize) const { aSize = mSize; }
// XXXmikeh - legacy, remove these when the Camera app is updated
uint32_t Width() const { return mSize.mWidth; }
uint32_t Height() const { return mSize.mHeight; }
protected:
virtual ~CameraRecorderVideoProfile();
nsCOMPtr<nsISupports> mParent;
const nsString mCodec;
uint32_t mBitrate;
uint32_t mFramerate;
dom::CameraSize mSize;
private:
DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderVideoProfile);
};
/**
* CameraRecorderAudioProfile
*/
class CameraRecorderAudioProfile MOZ_FINAL : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderAudioProfile)
explicit CameraRecorderAudioProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile::Audio& aProfile);
nsISupports* GetParentObject() const { return mParent; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
uint32_t BitsPerSecond() const { return mBitrate; }
uint32_t SamplesPerSecond() const { return mSamplerate; }
uint32_t Channels() const { return mChannels; }
void GetCodec(nsAString& aCodec) const { aCodec = mCodec; }
protected:
virtual ~CameraRecorderAudioProfile();
nsCOMPtr<nsISupports> mParent;
const nsString mCodec;
uint32_t mBitrate;
uint32_t mSamplerate;
uint32_t mChannels;
private:
DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderAudioProfile);
};
/**
* CameraRecorderProfile
*/
class CameraRecorderProfile MOZ_FINAL : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderProfile)
explicit CameraRecorderProfile(nsISupports* aParent,
const ICameraControl::RecorderProfile& aProfile);
nsISupports* GetParentObject() const { return mParent; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
void GetMimeType(nsAString& aMimeType) const { aMimeType = mMimeType; }
CameraRecorderVideoProfile* Video() { return mVideo; }
CameraRecorderAudioProfile* Audio() { return mAudio; }
void GetName(nsAString& aName) const { aName = mName; }
void
GetContainerFormat(nsAString& aContainerFormat) const
{
aContainerFormat = mContainerFormat;
}
protected:
virtual ~CameraRecorderProfile();
nsCOMPtr<nsISupports> mParent;
const nsString mName;
const nsString mContainerFormat;
const nsString mMimeType;
nsRefPtr<CameraRecorderVideoProfile> mVideo;
nsRefPtr<CameraRecorderAudioProfile> mAudio;
private:
DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderProfile);
};
/**
* CameraRecorderProfiles
*/
class CameraRecorderProfiles MOZ_FINAL : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderProfiles)
explicit CameraRecorderProfiles(nsISupports* aParent,
ICameraControl* aCameraControl);
nsISupports* GetParentObject() const { return mParent; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
CameraRecorderProfile* NamedGetter(const nsAString& aName, bool& aFound);
bool NameIsEnumerable(const nsAString& aName);
void GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames);
protected:
virtual ~CameraRecorderProfiles();
nsCOMPtr<nsISupports> mParent;
nsRefPtr<ICameraControl> mCameraControl;
nsRefPtrHashtable<nsStringHashKey, CameraRecorderProfile> mProfiles;
private:
DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderProfiles);
};
/**
* CameraCapabilities
*/
class CameraCapabilities MOZ_FINAL : public nsISupports
, public nsWrapperCache
{
@ -40,45 +190,38 @@ public:
// Great Renaming proposed in bug 983177.
static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
explicit CameraCapabilities(nsPIDOMWindow* aWindow);
// Populate the camera capabilities interface from the specific
// camera control object.
//
// Return values:
// - NS_OK on success;
// - NS_ERROR_INVALID_ARG if 'aCameraControl' is null.
nsresult Populate(ICameraControl* aCameraControl);
explicit CameraCapabilities(nsPIDOMWindow* aWindow,
ICameraControl* aCameraControl);
nsPIDOMWindow* GetParentObject() const { return mWindow; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
void GetPreviewSizes(nsTArray<CameraSize>& aRetVal) const;
void GetPictureSizes(nsTArray<CameraSize>& aRetVal) const;
void GetThumbnailSizes(nsTArray<CameraSize>& aRetVal) const;
void GetVideoSizes(nsTArray<CameraSize>& aRetVal) const;
void GetFileFormats(nsTArray<nsString>& aRetVal) const;
void GetWhiteBalanceModes(nsTArray<nsString>& aRetVal) const;
void GetSceneModes(nsTArray<nsString>& aRetVal) const;
void GetEffects(nsTArray<nsString>& aRetVal) const;
void GetFlashModes(nsTArray<nsString>& aRetVal) const;
void GetFocusModes(nsTArray<nsString>& aRetVal) const;
void GetZoomRatios(nsTArray<double>& aRetVal) const;
uint32_t MaxFocusAreas() const;
uint32_t MaxMeteringAreas() const;
uint32_t MaxDetectedFaces() const;
double MinExposureCompensation() const;
double MaxExposureCompensation() const;
double ExposureCompensationStep() const;
void GetRecorderProfiles(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval) const;
void GetIsoModes(nsTArray<nsString>& aRetVal) const;
void GetPreviewSizes(nsTArray<CameraSize>& aRetVal);
void GetPictureSizes(nsTArray<CameraSize>& aRetVal);
void GetThumbnailSizes(nsTArray<CameraSize>& aRetVal);
void GetVideoSizes(nsTArray<CameraSize>& aRetVal);
void GetFileFormats(nsTArray<nsString>& aRetVal);
void GetWhiteBalanceModes(nsTArray<nsString>& aRetVal);
void GetSceneModes(nsTArray<nsString>& aRetVal);
void GetEffects(nsTArray<nsString>& aRetVal);
void GetFlashModes(nsTArray<nsString>& aRetVal);
void GetFocusModes(nsTArray<nsString>& aRetVal);
void GetZoomRatios(nsTArray<double>& aRetVal);
uint32_t MaxFocusAreas();
uint32_t MaxMeteringAreas();
uint32_t MaxDetectedFaces();
double MinExposureCompensation();
double MaxExposureCompensation();
double ExposureCompensationStep();
void GetIsoModes(nsTArray<nsString>& aRetVal);
CameraRecorderProfiles* RecorderProfiles();
protected:
~CameraCapabilities();
nsresult TranslateToDictionary(ICameraControl* aCameraControl,
uint32_t aKey, nsTArray<CameraSize>& aSizes);
nsresult TranslateToDictionary(uint32_t aKey, nsTArray<CameraSize>& aSizes);
nsTArray<CameraSize> mPreviewSizes;
nsTArray<CameraSize> mPictureSizes;
@ -103,10 +246,12 @@ protected:
double mMaxExposureCompensation;
double mExposureCompensationStep;
nsRefPtr<RecorderProfileManager> mRecorderProfileManager;
JS::Heap<JS::Value> mRecorderProfiles;
nsRefPtr<nsPIDOMWindow> mWindow;
nsRefPtr<ICameraControl> mCameraControl;
nsRefPtr<CameraRecorderProfiles> mRecorderProfiles;
private:
DISALLOW_EVIL_CONSTRUCTORS(CameraCapabilities);
};
} // namespace dom

View File

@ -664,12 +664,7 @@ nsDOMCameraControl::Capabilities()
nsRefPtr<CameraCapabilities> caps = mCapabilities;
if (!caps) {
caps = new CameraCapabilities(mWindow);
nsresult rv = caps->Populate(mCameraControl);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGW("Failed to populate camera capabilities (%d)\n", rv);
return nullptr;
}
caps = new CameraCapabilities(mWindow, mCameraControl);
mCapabilities = caps;
}
@ -1140,10 +1135,9 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
MOZ_ASSERT(NS_IsMainThread());
ErrorResult ignored;
DOM_CAMERA_LOGI("DOM OnHardwareStateChange(%d)\n", aState);
switch (aState) {
case CameraControlListener::kHardwareOpen:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
{
// The hardware is open, so we can return a camera to JS, even if
// the preview hasn't started yet.
@ -1164,6 +1158,7 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
break;
case CameraControlListener::kHardwareClosed:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
{
nsRefPtr<Promise> promise = mReleasePromise.forget();
if (promise || mReleaseOnSuccessCb) {
@ -1188,6 +1183,7 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
break;
default:
DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
}
}
@ -1402,7 +1398,7 @@ nsDOMCameraControl::OnAutoFocusMoving(bool aIsMoving)
void
nsDOMCameraControl::OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces)
{
DOM_CAMERA_LOGI("DOM OnFacesDetected %u face(s)\n", aFaces.Length());
DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length());
MOZ_ASSERT(NS_IsMainThread());
Sequence<OwningNonNull<DOMCameraDetectedFace> > faces;

View File

@ -59,7 +59,7 @@ nsDOMCameraManager::nsDOMCameraManager(nsPIDOMWindow* aWindow)
, mWindow(aWindow)
{
/* member initializers and constructor code */
DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%" PRIx64 "\n", __func__, __LINE__, this, mWindowId);
MOZ_COUNT_CTOR(nsDOMCameraManager);
}
@ -364,7 +364,7 @@ nsDOMCameraManager::PermissionCancelled(uint32_t aCameraId,
void
nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
{
DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%" PRIx64 "\n", aDOMCameraControl, mWindowId);
MOZ_ASSERT(NS_IsMainThread());
// Put the camera control into the hash table
@ -379,7 +379,7 @@ nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
void
nsDOMCameraManager::Shutdown(uint64_t aWindowId)
{
DOM_CAMERA_LOGI(">>> Shutdown( aWindowId = 0x%llx )\n", aWindowId);
DOM_CAMERA_LOGI(">>> Shutdown( aWindowId = 0x%" PRIx64 " )\n", aWindowId);
MOZ_ASSERT(NS_IsMainThread());
CameraControls* controls = sActiveWindows->Get(aWindowId);

View File

@ -45,6 +45,9 @@ public:
virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
virtual nsresult GetRecorderProfiles(nsTArray<nsString>& aProfiles) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
virtual RecorderProfile* GetProfileInfo(const nsAString& aProfile) MOZ_OVERRIDE { return nullptr; }
nsresult PushParameters() { return NS_ERROR_NOT_INITIALIZED; }
nsresult PullParameters() { return NS_ERROR_NOT_INITIALIZED; }
@ -63,7 +66,6 @@ protected:
virtual nsresult StopRecordingImpl() { return NS_ERROR_NOT_INITIALIZED; }
virtual nsresult PushParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
virtual nsresult PullParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE { return nullptr; }
private:
FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;

View File

@ -71,11 +71,8 @@ nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
, mAutoFlashModeOverridden(false)
, mSeparateVideoAndPreviewSizesSupported(false)
, mDeferConfigUpdate(0)
, mMediaProfiles(nullptr)
, mRecorder(nullptr)
, mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
, mProfileManager(nullptr)
, mRecorderProfile(nullptr)
, mVideoFile(nullptr)
, mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
{
@ -145,6 +142,7 @@ nsGonkCameraControl::Initialize()
}
DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
mCurrentConfiguration.mRecorderProfile.Truncate();
// Initialize our camera configuration database.
PullParametersImpl();
@ -305,9 +303,6 @@ nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
// remove any existing recorder profile
mRecorderProfile = nullptr;
nsresult rv = SetPreviewSize(aConfig.mPreviewSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@ -960,7 +955,7 @@ nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescri
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
NS_ENSURE_TRUE(mRecorderProfile, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(!mCurrentConfiguration.mRecorderProfile.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_FALSE(mRecorder, NS_ERROR_FAILURE);
/**
@ -1368,32 +1363,25 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
// read preferences for camcorder
mMediaProfiles = MediaProfiles::getInstance();
nsAutoCString profile = NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile);
mRecorderProfile = GetGonkRecorderProfileManager().take()->Get(profile.get());
if (!mRecorderProfile) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n", profile.get());
RecorderProfile* profile;
if (!mRecorderProfiles.Get(aConfig.mRecorderProfile, &profile)) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
return NS_ERROR_INVALID_ARG;
}
const GonkRecorderVideoProfile* video = mRecorderProfile->GetGonkVideoProfile();
int width = video->GetWidth();
int height = video->GetHeight();
int fps = video->GetFramerate();
if (fps == -1 || width < 0 || height < 0) {
DOM_CAMERA_LOGE("Can't configure preview with fps=%d, width=%d, height=%d\n",
fps, width, height);
mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
const RecorderProfile::Video& video(profile->GetVideo());
const Size& size = video.GetSize();
int fps = video.GetFramesPerSecond();
if (fps <= 0 || size.width <= 0 || size.height <= 0) {
DOM_CAMERA_LOGE("Can't configure video with fps=%d, width=%d, height=%d\n",
fps, size.width, size.height);
return NS_ERROR_FAILURE;
}
PullParametersImpl();
Size size;
size.width = static_cast<uint32_t>(width);
size.height = static_cast<uint32_t>(height);
{
ICameraControlParameterSetAutoEnter set(this);
nsresult rv;
@ -1599,8 +1587,12 @@ nsGonkCameraControl::SetupRecording(int aFd, int aRotation,
mRecorder = new GonkRecorder();
CHECK_SETARG_RETURN(mRecorder->init(), NS_ERROR_FAILURE);
nsresult rv = mRecorderProfile->ConfigureRecorder(mRecorder);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv =
GonkRecorderProfile::ConfigureRecorder(*mRecorder, mCameraId,
mCurrentConfiguration.mRecorderProfile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
CHECK_SETARG_RETURN(mRecorder->setCamera(mCameraHw), NS_ERROR_FAILURE);
@ -1671,27 +1663,76 @@ nsGonkCameraControl::StopImpl()
return NS_OK;
}
already_AddRefed<GonkRecorderProfileManager>
nsGonkCameraControl::GetGonkRecorderProfileManager()
nsresult
nsGonkCameraControl::LoadRecorderProfiles()
{
if (!mProfileManager) {
nsTArray<Size> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
NS_ENSURE_SUCCESS(rv, nullptr);
if (mRecorderProfiles.Count() == 0) {
nsTArray<nsRefPtr<RecorderProfile>> profiles;
nsresult rv = GonkRecorderProfile::GetAll(mCameraId, profiles);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mProfileManager = new GonkRecorderProfileManager(mCameraId);
mProfileManager->SetSupportedResolutions(sizes);
nsTArray<Size> sizes;
rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_NOT_AVAILABLE;
}
// Limit profiles to those video sizes supported by the camera hardware...
for (nsTArray<RecorderProfile>::size_type i = 0; i < profiles.Length(); ++i) {
int width = profiles[i]->GetVideo().GetSize().width;
int height = profiles[i]->GetVideo().GetSize().height;
if (width < 0 || height < 0) {
DOM_CAMERA_LOGW("Ignoring weird profile '%s' with width and/or height < 0\n",
NS_ConvertUTF16toUTF8(profiles[i]->GetName()).get());
continue;
}
for (nsTArray<Size>::size_type n = 0; n < sizes.Length(); ++n) {
if (static_cast<uint32_t>(width) == sizes[n].width &&
static_cast<uint32_t>(height) == sizes[n].height) {
mRecorderProfiles.Put(profiles[i]->GetName(), profiles[i]);
break;
}
}
}
}
nsRefPtr<GonkRecorderProfileManager> profileMgr = mProfileManager;
return profileMgr.forget();
return NS_OK;
}
already_AddRefed<RecorderProfileManager>
nsGonkCameraControl::GetRecorderProfileManagerImpl()
/* static */ PLDHashOperator
nsGonkCameraControl::Enumerate(const nsAString& aProfileName,
RecorderProfile* aProfile,
void* aUserArg)
{
nsRefPtr<RecorderProfileManager> profileMgr = GetGonkRecorderProfileManager();
return profileMgr.forget();
nsTArray<nsString>* profiles = static_cast<nsTArray<nsString>*>(aUserArg);
MOZ_ASSERT(profiles);
profiles->AppendElement(aProfileName);
return PL_DHASH_NEXT;
}
nsresult
nsGonkCameraControl::GetRecorderProfiles(nsTArray<nsString>& aProfiles)
{
nsresult rv = LoadRecorderProfiles();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aProfiles.Clear();
mRecorderProfiles.EnumerateRead(Enumerate, static_cast<void*>(&aProfiles));
return NS_OK;
}
ICameraControl::RecorderProfile*
nsGonkCameraControl::GetProfileInfo(const nsAString& aProfile)
{
RecorderProfile* profile;
if (!mRecorderProfiles.Get(aProfile, &profile)) {
return nullptr;
}
return profile;
}
void

View File

@ -18,6 +18,7 @@
#define DOM_CAMERA_GONKCAMERACONTROL_H
#include "base/basictypes.h"
#include "nsRefPtrHashtable.h"
#include <media/MediaProfiles.h>
#include "mozilla/ReentrantMonitor.h"
#include "DeviceStorage.h"
@ -79,6 +80,10 @@ public:
virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE;
virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE;
virtual nsresult GetRecorderProfiles(nsTArray<nsString>& aProfiles) MOZ_OVERRIDE;
virtual ICameraControl::RecorderProfile*
GetProfileInfo(const nsAString& aProfile) MOZ_OVERRIDE;
nsresult PushParameters();
nsresult PullParameters();
@ -120,8 +125,6 @@ protected:
virtual nsresult ResumeContinuousFocusImpl() MOZ_OVERRIDE;
virtual nsresult PushParametersImpl() MOZ_OVERRIDE;
virtual nsresult PullParametersImpl() MOZ_OVERRIDE;
virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE;
already_AddRefed<GonkRecorderProfileManager> GetGonkRecorderProfileManager();
nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes,
uint64_t aMaxVideoLengthMs);
@ -131,6 +134,11 @@ protected:
nsresult PausePreview();
nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
nsresult LoadRecorderProfiles();
static PLDHashOperator Enumerate(const nsAString& aProfileName,
RecorderProfile* aProfile,
void* aUserArg);
friend class SetPictureSize;
friend class SetThumbnailSize;
nsresult SetPictureSize(const Size& aSize);
@ -157,16 +165,14 @@ protected:
nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
android::MediaProfiles* mMediaProfiles;
nsRefPtr<android::GonkRecorder> mRecorder;
// Touching mRecorder happens inside this monitor because the destructor
// can run on any thread, and we need to be able to clean up properly if
// GonkCameraControl goes away.
ReentrantMonitor mRecorderMonitor;
// Camcorder profile settings for the desired quality level
nsRefPtr<GonkRecorderProfileManager> mProfileManager;
nsRefPtr<GonkRecorderProfile> mRecorderProfile;
// Supported recorder profiles
nsRefPtrHashtable<nsStringHashKey, RecorderProfile> mRecorderProfiles;
nsRefPtr<DeviceStorageFile> mVideoFile;
nsString mFileFormat;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Mozilla Foundation
* Copyright (C) 2012-2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
#include "GonkRecorderProfiles.h"
#include <media/MediaProfiles.h>
#include "nsMimeTypes.h"
#include "GonkRecorder.h"
#include "CameraControlImpl.h"
#include "CameraCommon.h"
@ -38,216 +39,318 @@ static struct {
{ nullptr, 0 }
};
static MediaProfiles* sMediaProfiles = nullptr;
/* static */ nsClassHashtable<nsUint32HashKey, ProfileHashtable> GonkRecorderProfile::sProfiles;
/* static */ android::MediaProfiles* sMediaProfiles = nullptr;
static bool
IsQualitySupported(uint32_t aCameraId, uint32_t aQualityIndex)
static MediaProfiles*
GetMediaProfiles()
{
if (!sMediaProfiles) {
sMediaProfiles = MediaProfiles::getInstance();
}
camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aQualityIndex].quality);
return sMediaProfiles->hasCamcorderProfile(static_cast<int>(aCameraId), q);
MOZ_ASSERT(sMediaProfiles);
return sMediaProfiles;
}
static bool
IsProfileSupported(uint32_t aCameraId, uint32_t aProfileIndex)
{
MediaProfiles* profiles = GetMediaProfiles();
camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aProfileIndex].quality);
return profiles->hasCamcorderProfile(static_cast<int>(aCameraId), q);
}
static int
GetProfileParam(uint32_t aCameraId, uint32_t aQualityIndex, const char* aParam)
GetProfileParameter(uint32_t aCameraId, uint32_t aProfileIndex, const char* aParameter)
{
if (!sMediaProfiles) {
sMediaProfiles = MediaProfiles::getInstance();
}
camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aQualityIndex].quality);
return sMediaProfiles->getCamcorderProfileParamByName(aParam, static_cast<int>(aCameraId), q);
MediaProfiles* profiles = GetMediaProfiles();
camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aProfileIndex].quality);
return profiles->getCamcorderProfileParamByName(aParameter, static_cast<int>(aCameraId), q);
}
/**
* Recorder profile.
*/
static RecorderProfile::FileFormat
TranslateFileFormat(output_format aFileFormat)
/* static */ bool
GonkRecorderVideo::Translate(video_encoder aCodec, nsAString& aCodecName)
{
switch (aFileFormat) {
case OUTPUT_FORMAT_THREE_GPP: return RecorderProfile::THREE_GPP;
case OUTPUT_FORMAT_MPEG_4: return RecorderProfile::MPEG4;
default: return RecorderProfile::UNKNOWN;
switch (aCodec) {
case VIDEO_ENCODER_H263:
aCodecName.AssignASCII("h263");
break;
case VIDEO_ENCODER_H264:
aCodecName.AssignASCII("h264");
break;
case VIDEO_ENCODER_MPEG_4_SP:
aCodecName.AssignASCII("mpeg4sp");
break;
default:
return false;
}
return true;
}
GonkRecorderProfile::GonkRecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: RecorderProfileBase<GonkRecorderAudioProfile, GonkRecorderVideoProfile>(aCameraId, aQualityIndex)
int
GonkRecorderVideo::GetProfileParameter(const char* aParameter)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
mPlatformOutputFormat = static_cast<output_format>(GetProfileParam(mCameraId, mQualityIndex, "file.format"));
mFileFormat = TranslateFileFormat(mPlatformOutputFormat);
if (aQualityIndex < PROFILE_COUNT) {
mName = ProfileList[aQualityIndex].name;
DOM_CAMERA_LOGI("Created camera %d profile index %d: '%s'\n", mCameraId, mQualityIndex, mName);
}
return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
}
GonkRecorderProfile::~GonkRecorderProfile()
GonkRecorderVideo::GonkRecorderVideo(uint32_t aCameraId, uint32_t aProfileIndex)
: mCameraId(aCameraId)
, mProfileIndex(aProfileIndex)
, mIsValid(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mPlatformEncoder = static_cast<video_encoder>(GetProfileParameter("vid.codec"));
bool isValid = Translate(mPlatformEncoder, mCodec);
int v = GetProfileParameter("vid.width");
if (v >= 0) {
mSize.width = v;
} else {
isValid = false;
}
v = GetProfileParameter("vid.height");
if (v >= 0) {
mSize.height = v;
} else {
isValid = false;
}
v = GetProfileParameter("vid.bps");
if (v >= 0) {
mBitsPerSecond = v;
} else {
isValid = false;
}
v = GetProfileParameter("vid.fps");
if (v >= 0) {
mFramesPerSecond = v;
} else {
isValid = false;
}
mIsValid = isValid;
}
/* static */ bool
GonkRecorderAudio::Translate(audio_encoder aCodec, nsAString& aCodecName)
{
switch (aCodec) {
case AUDIO_ENCODER_AMR_NB:
aCodecName.AssignASCII("amrnb");
break;
case AUDIO_ENCODER_AMR_WB:
aCodecName.AssignASCII("amrwb");
break;
case AUDIO_ENCODER_AAC:
aCodecName.AssignASCII("aac");
break;
default:
return false;
}
return true;
}
int
GonkRecorderAudio::GetProfileParameter(const char* aParameter)
{
return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
}
GonkRecorderAudio::GonkRecorderAudio(uint32_t aCameraId, uint32_t aProfileIndex)
: mCameraId(aCameraId)
, mProfileIndex(aProfileIndex)
, mIsValid(false)
{
mPlatformEncoder = static_cast<audio_encoder>(GetProfileParameter("aud.codec"));
bool isValid = Translate(mPlatformEncoder, mCodec);
int v = GetProfileParameter("aud.ch");
if (v >= 0) {
mChannels = v;
} else {
isValid = false;
}
v = GetProfileParameter("aud.bps");
if (v >= 0) {
mBitsPerSecond = v;
} else {
isValid = false;
}
v = GetProfileParameter("aud.hz");
if (v >= 0) {
mSamplesPerSecond = v;
} else {
isValid = false;
}
mIsValid = isValid;
}
/* static */ bool
GonkRecorderProfile::Translate(output_format aContainer, nsAString& aContainerName)
{
switch (aContainer) {
case OUTPUT_FORMAT_THREE_GPP:
aContainerName.AssignASCII("3gp");
break;
case OUTPUT_FORMAT_MPEG_4:
aContainerName.AssignASCII("mp4");
break;
default:
return false;
}
return true;
}
/* static */ bool
GonkRecorderProfile::GetMimeType(output_format aContainer, nsAString& aMimeType)
{
switch (aContainer) {
case OUTPUT_FORMAT_THREE_GPP:
aMimeType.AssignASCII(VIDEO_3GPP);
break;
case OUTPUT_FORMAT_MPEG_4:
aMimeType.AssignASCII(VIDEO_MP4);
break;
default:
return false;
}
return true;
}
int
GonkRecorderProfile::GetProfileParameter(const char* aParameter)
{
return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
}
GonkRecorderProfile::GonkRecorderProfile(uint32_t aCameraId,
uint32_t aProfileIndex,
const nsAString& aName)
: GonkRecorderProfileBase<GonkRecorderAudio, GonkRecorderVideo>(aCameraId,
aProfileIndex, aName)
, mCameraId(aCameraId)
, mProfileIndex(aProfileIndex)
, mIsValid(false)
{
mOutputFormat = static_cast<output_format>(GetProfileParameter("file.format"));
bool isValid = Translate(mOutputFormat, mContainer);
isValid = GetMimeType(mOutputFormat, mMimeType) ? isValid : false;
mIsValid = isValid && mAudio.IsValid() && mVideo.IsValid();
}
/* static */ PLDHashOperator
GonkRecorderProfile::Enumerate(const nsAString& aProfileName,
GonkRecorderProfile* aProfile,
void* aUserArg)
{
nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>* profiles =
static_cast<nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>*>(aUserArg);
MOZ_ASSERT(profiles);
profiles->AppendElement(aProfile);
return PL_DHASH_NEXT;
}
/* static */
ProfileHashtable*
GonkRecorderProfile::GetProfileHashtable(uint32_t aCameraId)
{
ProfileHashtable* profiles = sProfiles.Get(aCameraId);
if (!profiles) {
profiles = new ProfileHashtable;
sProfiles.Put(aCameraId, profiles);
for (uint32_t i = 0; ProfileList[i].name; ++i) {
if (IsProfileSupported(aCameraId, i)) {
DOM_CAMERA_LOGI("Profile %d '%s' supported by platform\n", i, ProfileList[i].name);
nsAutoString name;
name.AssignASCII(ProfileList[i].name);
nsRefPtr<GonkRecorderProfile> profile = new GonkRecorderProfile(aCameraId, i, name);
if (!profile->IsValid()) {
DOM_CAMERA_LOGE("Profile %d '%s' is not valid\n", i, ProfileList[i].name);
continue;
}
profiles->Put(name, profile);
} else {
DOM_CAMERA_LOGI("Profile %d '%s' not supported by platform\n", i, ProfileList[i].name);
}
}
}
return profiles;
}
/* static */ nsresult
GonkRecorderProfile::GetAll(uint32_t aCameraId,
nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>& aProfiles)
{
ProfileHashtable* profiles = GetProfileHashtable(aCameraId);
if (!profiles) {
return NS_ERROR_FAILURE;
}
aProfiles.Clear();
profiles->EnumerateRead(Enumerate, static_cast<void*>(&aProfiles));
return NS_OK;
}
nsresult
GonkRecorderProfile::ConfigureRecorder(GonkRecorder* aRecorder)
GonkRecorderProfile::ConfigureRecorder(GonkRecorder& aRecorder)
{
if (!aRecorder) {
DOM_CAMERA_LOGW("ConfigureRecorder() called with null aRecorder\n");
return NS_ERROR_INVALID_ARG;
}
static const size_t SIZE = 256;
char buffer[SIZE];
// set all the params
CHECK_SETARG(aRecorder->setAudioSource(AUDIO_SOURCE_CAMCORDER));
CHECK_SETARG(aRecorder->setVideoSource(VIDEO_SOURCE_CAMERA));
CHECK_SETARG(aRecorder->setOutputFormat(GetOutputFormat()));
CHECK_SETARG(aRecorder->setVideoFrameRate(mVideo.GetFramerate()));
CHECK_SETARG(aRecorder->setVideoSize(mVideo.GetWidth(), mVideo.GetHeight()));
CHECK_SETARG(aRecorder->setVideoEncoder(mVideo.GetPlatformCodec()));
CHECK_SETARG(aRecorder->setAudioEncoder(mAudio.GetPlatformCodec()));
CHECK_SETARG(aRecorder.setAudioSource(AUDIO_SOURCE_CAMCORDER));
CHECK_SETARG(aRecorder.setVideoSource(VIDEO_SOURCE_CAMERA));
CHECK_SETARG(aRecorder.setOutputFormat(mOutputFormat));
CHECK_SETARG(aRecorder.setVideoFrameRate(mVideo.GetFramesPerSecond()));
CHECK_SETARG(aRecorder.setVideoSize(mVideo.GetSize().width, mVideo.GetSize().height));
CHECK_SETARG(aRecorder.setVideoEncoder(mVideo.GetPlatformEncoder()));
CHECK_SETARG(aRecorder.setAudioEncoder(mAudio.GetPlatformEncoder()));
snprintf(buffer, SIZE, "video-param-encoding-bitrate=%d", mVideo.GetBitrate());
CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
snprintf(buffer, SIZE, "video-param-encoding-bitrate=%d", mVideo.GetBitsPerSecond());
CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
snprintf(buffer, SIZE, "audio-param-encoding-bitrate=%d", mAudio.GetBitrate());
CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
snprintf(buffer, SIZE, "audio-param-encoding-bitrate=%d", mAudio.GetBitsPerSecond());
CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
snprintf(buffer, SIZE, "audio-param-number-of-channels=%d", mAudio.GetChannels());
CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
snprintf(buffer, SIZE, "audio-param-sampling-rate=%d", mAudio.GetSamplerate());
CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
snprintf(buffer, SIZE, "audio-param-sampling-rate=%d", mAudio.GetSamplesPerSecond());
CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
return NS_OK;
}
/**
* Recorder audio profile.
*/
static RecorderAudioProfile::Codec
TranslateAudioCodec(audio_encoder aCodec)
/* static */ nsresult
GonkRecorderProfile::ConfigureRecorder(android::GonkRecorder& aRecorder,
uint32_t aCameraId,
const nsAString& aProfileName)
{
switch (aCodec) {
case AUDIO_ENCODER_AMR_NB: return RecorderAudioProfile::AMRNB;
case AUDIO_ENCODER_AMR_WB: return RecorderAudioProfile::AMRWB;
case AUDIO_ENCODER_AAC: return RecorderAudioProfile::AAC;
default: return RecorderAudioProfile::UNKNOWN;
}
}
GonkRecorderAudioProfile::GonkRecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: RecorderAudioProfile(aCameraId, aQualityIndex)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
mPlatformCodec = static_cast<audio_encoder>(GetProfileParam(mCameraId, mQualityIndex, "aud.codec"));
mCodec = TranslateAudioCodec(mPlatformCodec);
mBitrate = GetProfileParam(mCameraId, mQualityIndex, "aud.bps");
mSamplerate = GetProfileParam(mCameraId, mQualityIndex, "aud.hz");
mChannels = GetProfileParam(mCameraId, mQualityIndex, "aud.ch");
}
GonkRecorderAudioProfile::~GonkRecorderAudioProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
/**
* Recorder video profile.
*/
static RecorderVideoProfile::Codec
TranslateVideoCodec(video_encoder aCodec)
{
switch (aCodec) {
case VIDEO_ENCODER_H263: return RecorderVideoProfile::H263;
case VIDEO_ENCODER_H264: return RecorderVideoProfile::H264;
case VIDEO_ENCODER_MPEG_4_SP: return RecorderVideoProfile::MPEG4SP;
default: return RecorderVideoProfile::UNKNOWN;
}
}
GonkRecorderVideoProfile::GonkRecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
: RecorderVideoProfile(aCameraId, aQualityIndex)
{
DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
mPlatformCodec = static_cast<video_encoder>(GetProfileParam(mCameraId, mQualityIndex, "vid.codec"));
mCodec = TranslateVideoCodec(mPlatformCodec);
mBitrate = GetProfileParam(mCameraId, mQualityIndex, "vid.bps");
mFramerate = GetProfileParam(mCameraId, mQualityIndex, "vid.fps");
mWidth = GetProfileParam(mCameraId, mQualityIndex, "vid.width");
mHeight = GetProfileParam(mCameraId, mQualityIndex, "vid.height");
}
GonkRecorderVideoProfile::~GonkRecorderVideoProfile()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
GonkRecorderProfileManager::GonkRecorderProfileManager(uint32_t aCameraId)
: RecorderProfileManager(aCameraId)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mMaxQualityIndex = sizeof(ProfileList) / sizeof(ProfileList[0]) - 1;
}
GonkRecorderProfileManager::~GonkRecorderProfileManager()
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
}
bool
GonkRecorderProfileManager::IsSupported(uint32_t aQualityIndex) const
{
if (!IsQualitySupported(mCameraId, aQualityIndex)) {
// This profile is not supported
return false;
ProfileHashtable* profiles = GetProfileHashtable(aCameraId);
if (!profiles) {
return NS_ERROR_FAILURE;
}
int width = GetProfileParam(mCameraId, aQualityIndex, "vid.width");
int height = GetProfileParam(mCameraId, aQualityIndex, "vid.height");
if (width == -1 || height == -1) {
// This would be unexpected, but we handle it just in case
DOM_CAMERA_LOGE("Camera %d recorder profile %d has width=%d, height=%d\n", mCameraId, aQualityIndex, width, height);
return false;
GonkRecorderProfile* profile;
if (!profiles->Get(aProfileName, &profile)) {
return NS_ERROR_INVALID_ARG;
}
for (uint32_t i = 0; i < mSupportedSizes.Length(); ++i) {
if (static_cast<uint32_t>(width) == mSupportedSizes[i].width &&
static_cast<uint32_t>(height) == mSupportedSizes[i].height)
{
return true;
}
}
return false;
}
already_AddRefed<RecorderProfile>
GonkRecorderProfileManager::Get(uint32_t aQualityIndex) const
{
// This overrides virtual RecorderProfileManager::Get(...)
DOM_CAMERA_LOGT("%s:%d : aQualityIndex=%d\n", __func__, __LINE__, aQualityIndex);
nsRefPtr<RecorderProfile> profile = new GonkRecorderProfile(mCameraId, aQualityIndex);
return profile.forget();
}
already_AddRefed<GonkRecorderProfile>
GonkRecorderProfileManager::Get(const char* aProfileName) const
{
DOM_CAMERA_LOGT("%s:%d : aProfileName='%s'\n", __func__, __LINE__, aProfileName);
for (uint32_t i = 0; i < mMaxQualityIndex; ++i) {
if (strcmp(ProfileList[i].name, aProfileName) == 0) {
nsRefPtr<GonkRecorderProfile> profile = nullptr;
if (IsSupported(i)) {
profile = new GonkRecorderProfile(mCameraId, i);
return profile.forget();
}
return nullptr;
}
}
DOM_CAMERA_LOGW("Couldn't file recorder profile named '%s'\n", aProfileName);
return nullptr;
return profile->ConfigureRecorder(aRecorder);
}

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