mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Merge m-c to inbound. a=merge
CLOSED TREE
This commit is contained in:
commit
0884e151e6
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "62e4e9a9d7d888e136a1204406221c7df22be7c1",
|
||||
"revision": "6bf7a21e0dbf766076bfd1c15e59dea7f25a4c13",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 " ">
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
86
browser/components/loop/content/js/conversationAppStore.js
Normal file
86
browser/components/loop/content/js/conversationAppStore.js
Normal 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;
|
||||
|
||||
})();
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
})
|
||||
};
|
||||
})();
|
||||
|
@ -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"));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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() {
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"',
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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@ {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
41
browser/themes/shared/devedition/search.svg
Normal file
41
browser/themes/shared/devedition/search.svg
Normal 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 |
@ -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 |
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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'
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
772
dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
Normal file
772
dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
Normal 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
|
1565
dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h
Normal file
1565
dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h
Normal file
File diff suppressed because it is too large
Load Diff
1683
dom/bluetooth/bluedroid/BluetoothDaemonInterface.cpp
Normal file
1683
dom/bluetooth/bluedroid/BluetoothDaemonInterface.cpp
Normal file
File diff suppressed because it is too large
Load Diff
132
dom/bluetooth/bluedroid/BluetoothDaemonInterface.h
Normal file
132
dom/bluetooth/bluedroid/BluetoothDaemonInterface.h
Normal 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
|
32
dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.cpp
Normal file
32
dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.cpp
Normal 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
|
29
dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.h
Normal file
29
dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.h
Normal 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
|
326
dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp
Normal file
326
dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp
Normal 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
|
121
dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
Normal file
121
dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
Normal 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
|
@ -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']
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -34,8 +34,8 @@ public:
|
||||
|
||||
enum HardwareState
|
||||
{
|
||||
kHardwareOpen,
|
||||
kHardwareClosed
|
||||
kHardwareClosed,
|
||||
kHardwareOpen
|
||||
};
|
||||
virtual void OnHardwareStateChange(HardwareState aState) { }
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user