mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to fx-team
This commit is contained in:
commit
d778407bb2
2
CLOBBER
2
CLOBBER
@ -22,4 +22,4 @@
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 1024707 - It seems like changing 3 uuids is just too much!
|
||||
Bug 1038799 - And be wary of your ccache too.
|
||||
|
@ -664,6 +664,12 @@ pref("javascript.options.mem.gc_low_frequency_heap_growth", 120);
|
||||
pref("javascript.options.mem.high_water_mark", 6);
|
||||
pref("javascript.options.mem.gc_allocation_threshold_mb", 1);
|
||||
pref("javascript.options.mem.gc_decommit_threshold_mb", 1);
|
||||
#ifdef JSGC_GENERATIONAL
|
||||
pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
|
||||
#else
|
||||
pref("javascript.options.mem.gc_min_empty_chunk_count", 0);
|
||||
#endif
|
||||
pref("javascript.options.mem.gc_max_empty_chunk_count", 2);
|
||||
|
||||
// Show/Hide scrollbars when active/inactive
|
||||
pref("ui.showHideScrollbars", 1);
|
||||
@ -1007,7 +1013,7 @@ pref("services.sync.fxaccounts.enabled", true);
|
||||
pref("identity.fxaccounts.enabled", true);
|
||||
|
||||
// Mobile Identity API.
|
||||
pref("services.mobileid.server.uri", "https://msisdn-dev.stage.mozaws.net");
|
||||
pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
|
||||
|
||||
// Enable mapped array buffer
|
||||
pref("dom.mapped_arraybuffer.enabled", true);
|
||||
|
@ -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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<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="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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "8cd6c73ef83257a569d148e246108b2c161127bb",
|
||||
"revision": "6739781fb8d0f3ae8bff65d1093e74d9f21ed6e5",
|
||||
"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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
|
||||
|
@ -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="d29773d2a011825fd77d1c0915a96eb0911417b6"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="5edca8cd06409b8bf404de4adf7ea08fc2940fd7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7f792d756385bb894fba7645da59c67fe2c804bf"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -1511,11 +1511,16 @@ pref("shumway.disabled", true);
|
||||
// (This is intentionally on the high side; see bug 746055.)
|
||||
pref("image.mem.max_decoded_image_kb", 256000);
|
||||
|
||||
#ifdef MOZ_LOOP
|
||||
// Enable by default on nightly and aurora.
|
||||
#ifndef RELEASE_BUILD
|
||||
pref("loop.enabled", true);
|
||||
#else
|
||||
pref("loop.enabled", false);
|
||||
#endif
|
||||
|
||||
pref("loop.server", "https://loop.services.mozilla.com");
|
||||
pref("loop.do_not_disturb", false);
|
||||
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/Firefox-Long.ogg");
|
||||
#endif
|
||||
|
||||
// serverURL to be assigned by services team
|
||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||
|
@ -36,6 +36,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
|
||||
* delayedStartup.
|
||||
*/
|
||||
initialize: function() {
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
CustomizableUI.getWidget("loop-call-button").forWindow(window).node.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
MozLoopService.initialize();
|
||||
},
|
||||
};
|
||||
|
@ -172,9 +172,7 @@ let gInitialPages = [
|
||||
#include browser-feeds.js
|
||||
#include browser-fullScreen.js
|
||||
#include browser-fullZoom.js
|
||||
#ifdef MOZ_LOOP
|
||||
#include browser-loop.js
|
||||
#endif
|
||||
#include browser-places.js
|
||||
#include browser-plugins.js
|
||||
#include browser-safebrowsing.js
|
||||
@ -1188,9 +1186,7 @@ var gBrowserInit = {
|
||||
gDataNotificationInfoBar.init();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_LOOP
|
||||
LoopUI.initialize();
|
||||
#endif
|
||||
|
||||
gBrowserThumbnails.init();
|
||||
|
||||
|
@ -267,7 +267,6 @@
|
||||
noautofocus="true"
|
||||
position="topcenter topright"/>
|
||||
|
||||
#ifdef MOZ_LOOP
|
||||
<panel id="loop-notification-panel"
|
||||
class="loop-panel social-panel"
|
||||
type="arrow"
|
||||
@ -279,7 +278,6 @@
|
||||
type="arrow"
|
||||
orient="horizontal"
|
||||
hidden="true"/>
|
||||
#endif
|
||||
|
||||
<menupopup id="toolbar-context-menu"
|
||||
onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
|
||||
@ -671,11 +669,7 @@
|
||||
aria-label="&navbarCmd.label;"
|
||||
fullscreentoolbar="true" mode="icons" customizable="true"
|
||||
iconsize="small"
|
||||
#ifdef MOZ_LOOP
|
||||
defaultset="urlbar-container,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,loop-call-button,social-share-button,social-toolbar-item"
|
||||
#else
|
||||
defaultset="urlbar-container,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,social-share-button,social-toolbar-item"
|
||||
#endif
|
||||
customizationtarget="nav-bar-customization-target"
|
||||
overflowable="true"
|
||||
overflowbutton="nav-bar-overflow-button"
|
||||
@ -819,7 +813,7 @@
|
||||
oncommand="LoopUI.openCallPanel(event);"
|
||||
cui-areatype="toolbar">
|
||||
</toolbarbutton>
|
||||
#endif
|
||||
#endifhttps://hg.mozilla.org/mozilla-central/rev/cd712c340dd7
|
||||
|
||||
<toolbarbutton id="bookmarks-menu-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
@ -950,6 +944,21 @@
|
||||
cui-areatype="toolbar"
|
||||
aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
|
||||
|
||||
<!-- XXX Bug 1013989 will provide a label for the button -->
|
||||
<!-- This uses badged to be compatible with the social api code it shares.
|
||||
We may also want it to be badged in the future, for notification
|
||||
purposes. -->
|
||||
<toolbarbutton id="loop-call-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
persist="class"
|
||||
type="badged"
|
||||
removable="true"
|
||||
tooltiptext="&loopCallButton.tooltip;"
|
||||
oncommand="LoopUI.openCallPanel(event);"
|
||||
cui-areatype="toolbar"
|
||||
>
|
||||
</toolbarbutton>
|
||||
|
||||
|
||||
<toolbarbutton id="social-share-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
|
@ -100,7 +100,6 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xul",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
#ifdef MOZ_LOOP
|
||||
{ "loopconversation", "chrome://browser/content/loop/conversation.html",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
@ -113,7 +112,6 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::ENABLE_INDEXED_DB,
|
||||
// Shares an IndexedDB origin with about:loopconversation.
|
||||
"loopconversation" },
|
||||
#endif
|
||||
};
|
||||
static const int kRedirTotal = ArrayLength(kRedirMap);
|
||||
|
||||
|
@ -112,10 +112,8 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
|
||||
#endif
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "app-manager", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
#ifdef MOZ_LOOP
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
#endif
|
||||
#if defined(XP_WIN)
|
||||
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
|
||||
#elif defined(XP_MACOSX)
|
||||
|
@ -141,9 +141,7 @@
|
||||
|
||||
<panelview id="PanelUI-socialapi" flex="1"/>
|
||||
|
||||
#ifdef MOZ_LOOP
|
||||
<panelview id="PanelUI-loopapi" flex="1"/>
|
||||
#endif
|
||||
|
||||
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
|
||||
<label value="&feedsMenu.label;" class="panel-subview-header"/>
|
||||
|
@ -206,9 +206,7 @@ let CustomizableUIInternal = {
|
||||
"bookmarks-menu-button",
|
||||
"downloads-button",
|
||||
"home-button",
|
||||
#ifdef MOZ_LOOP
|
||||
"loop-call-button",
|
||||
#endif
|
||||
"social-share-button",
|
||||
],
|
||||
defaultCollapsed: false,
|
||||
|
@ -449,6 +449,11 @@ this.MozLoopService = {
|
||||
* push and loop servers.
|
||||
*/
|
||||
initialize: function() {
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If expiresTime is in the future then kick-off registration.
|
||||
if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
|
||||
this._startInitializeTimer();
|
||||
@ -481,6 +486,11 @@ this.MozLoopService = {
|
||||
* rejected with an error code or string.
|
||||
*/
|
||||
register: function(mockPushHandler) {
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
throw new Error("Loop is not enabled");
|
||||
}
|
||||
|
||||
return MozLoopServiceInternal.promiseRegisteredWithServers(mockPushHandler);
|
||||
},
|
||||
|
||||
|
@ -34,13 +34,6 @@ loop.Client = (function($) {
|
||||
}
|
||||
|
||||
Client.prototype = {
|
||||
/**
|
||||
* Converts from hours to seconds
|
||||
*/
|
||||
_hoursToSeconds: function(value) {
|
||||
return value * 60 * 60;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates a data object to confirm it has the specified properties.
|
||||
*
|
||||
@ -124,8 +117,7 @@ loop.Client = (function($) {
|
||||
|
||||
cb(null, this._validate(urlData, expectedCallUrlProperties));
|
||||
|
||||
var expiresHours = this._hoursToSeconds(urlData.expiresAt);
|
||||
this.mozLoop.noteCallUrlExpiry(expiresHours);
|
||||
this.mozLoop.noteCallUrlExpiry(urlData.expiresAt);
|
||||
} catch (err) {
|
||||
console.log("Error requesting call info", err);
|
||||
cb(err);
|
||||
|
@ -14,6 +14,7 @@ loop.shared.models = (function() {
|
||||
*/
|
||||
var ConversationModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
connected: false, // Session connected flag
|
||||
ongoing: false, // Ongoing call flag
|
||||
callerId: undefined, // Loop caller id
|
||||
loopToken: undefined, // Loop conversation token
|
||||
@ -38,11 +39,29 @@ loop.shared.models = (function() {
|
||||
*/
|
||||
session: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timeout value.
|
||||
* @type {Number}
|
||||
*/
|
||||
pendingCallTimeout: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timer.
|
||||
* @type {Number}
|
||||
*/
|
||||
_pendingCallTimer: undefined,
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {OT} sdk: SDK object.
|
||||
* Options:
|
||||
*
|
||||
* Required:
|
||||
* - {OT} sdk: OT SDK object.
|
||||
*
|
||||
* Optional:
|
||||
* - {Number} pendingCallTimeout: Pending call timeout in milliseconds
|
||||
* (default: 20000).
|
||||
*
|
||||
* @param {Object} attributes Attributes object.
|
||||
* @param {Object} options Options object.
|
||||
@ -53,6 +72,10 @@ loop.shared.models = (function() {
|
||||
throw new Error("missing required sdk");
|
||||
}
|
||||
this.sdk = options.sdk;
|
||||
this.pendingCallTimeout = options.pendingCallTimeout || 20000;
|
||||
|
||||
// Ensure that any pending call timer is cleared on disconnect/error
|
||||
this.on("session:ended session:error", this._clearPendingCallTimer, this);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -79,21 +102,38 @@ loop.shared.models = (function() {
|
||||
* @param {Object} options Options object
|
||||
*/
|
||||
initiate: function(options) {
|
||||
options = options || {};
|
||||
|
||||
// Outgoing call has never reached destination, closing - see bug 1020448
|
||||
function handleOutgoingCallTimeout() {
|
||||
/*jshint validthis:true */
|
||||
if (!this.get("ongoing")) {
|
||||
this.trigger("timeout").endSession();
|
||||
}
|
||||
}
|
||||
|
||||
function handleResult(err, sessionData) {
|
||||
/*jshint validthis:true */
|
||||
this._clearPendingCallTimer();
|
||||
|
||||
if (err) {
|
||||
this.trigger("session:error", new Error(
|
||||
"Retrieval of session information failed: HTTP " + err));
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX For incoming calls we might have more than one call queued.
|
||||
// For now, we'll just assume the first call is the right information.
|
||||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 990714 should fix this.
|
||||
if (!options.outgoing)
|
||||
if (options.outgoing) {
|
||||
// Setup pending call timeout.
|
||||
this._pendingCallTimer = setTimeout(
|
||||
handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
|
||||
} else {
|
||||
// XXX For incoming calls we might have more than one call queued.
|
||||
// For now, we'll just assume the first call is the right information.
|
||||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 990714 should fix this.
|
||||
sessionData = sessionData[0];
|
||||
}
|
||||
|
||||
this.setReady(sessionData);
|
||||
}
|
||||
@ -156,8 +196,17 @@ loop.shared.models = (function() {
|
||||
*/
|
||||
endSession: function() {
|
||||
this.session.disconnect();
|
||||
this.once("session:ended", this.stopListening, this);
|
||||
this.set("ongoing", false);
|
||||
this.set("ongoing", false)
|
||||
.once("session:ended", this.stopListening, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears current pending call timer, if any.
|
||||
*/
|
||||
_clearPendingCallTimer: function() {
|
||||
if (this._pendingCallTimer) {
|
||||
clearTimeout(this._pendingCallTimer);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -175,7 +224,7 @@ loop.shared.models = (function() {
|
||||
this.endSession();
|
||||
} else {
|
||||
this.trigger("session:connected");
|
||||
this.set("ongoing", true);
|
||||
this.set("connected", true);
|
||||
}
|
||||
},
|
||||
|
||||
@ -186,7 +235,8 @@ loop.shared.models = (function() {
|
||||
* @param {StreamEvent} event
|
||||
*/
|
||||
_streamCreated: function(event) {
|
||||
this.trigger("session:stream-created", event);
|
||||
this.set("ongoing", true)
|
||||
.trigger("session:stream-created", event);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -196,8 +246,9 @@ loop.shared.models = (function() {
|
||||
* @param {SessionDisconnectEvent} event
|
||||
*/
|
||||
_sessionDisconnected: function(event) {
|
||||
this.trigger("session:ended");
|
||||
this.set("ongoing", false);
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:ended");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -207,9 +258,11 @@ loop.shared.models = (function() {
|
||||
* @param {ConnectionEvent} event
|
||||
*/
|
||||
_connectionDestroyed: function(event) {
|
||||
this.trigger("session:peer-hungup", {
|
||||
connectionId: event.connection.connectionId
|
||||
});
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:peer-hungup", {
|
||||
connectionId: event.connection.connectionId
|
||||
});
|
||||
this.endSession();
|
||||
},
|
||||
|
||||
@ -220,7 +273,9 @@ loop.shared.models = (function() {
|
||||
* @param {ConnectionEvent} event
|
||||
*/
|
||||
_networkDisconnected: function(event) {
|
||||
this.trigger("session:network-disconnected");
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:network-disconnected");
|
||||
this.endSession();
|
||||
},
|
||||
});
|
||||
|
@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000})
|
||||
LOOP_PENDING_CALL_TIMEOUT := $(shell echo $${LOOP_PENDING_CALL_TIMEOUT-20000})
|
||||
NODE_LOCAL_BIN=./node_modules/.bin
|
||||
|
||||
install:
|
||||
@ -21,4 +22,7 @@ frontend:
|
||||
@echo "Not implemented yet."
|
||||
|
||||
config:
|
||||
@echo "var loop = loop || {};\nloop.config = {serverUrl: '`echo $(LOOP_SERVER_URL)`'};" > content/config.js
|
||||
@echo "var loop = loop || {};" > content/config.js
|
||||
@echo "loop.config = loop.config || {};" >> content/config.js
|
||||
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
|
||||
@echo "loop.config.pendingCallTimeout = `echo $(LOOP_PENDING_CALL_TIMEOUT)`;" >> content/config.js
|
||||
|
@ -16,11 +16,15 @@ Configuration
|
||||
|
||||
You will need to generate a configuration file, you can do so with:
|
||||
|
||||
$ make config
|
||||
$ make config
|
||||
|
||||
It will read the configuration from the `LOOP_SERVER_URL` env variable and
|
||||
generate the appropriate configuration file. This setting defines the root url
|
||||
of the loop server, without trailing slash.
|
||||
It will read the configuration from the following env variables and generate the
|
||||
appropriate configuration file:
|
||||
|
||||
- `LOOP_SERVER_URL` defines the root url of the loop server, without trailing
|
||||
slash (default: `http://localhost:5000`).
|
||||
- `LOOP_PENDING_CALL_TIMEOUT` defines the amount of time a pending outgoing call
|
||||
should be considered timed out, in milliseconds (default: `20000`).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
@ -119,6 +119,8 @@ loop.webapp = (function($, _, OT) {
|
||||
initialize: function() {
|
||||
// Load default view
|
||||
this.loadView(new HomeView());
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -146,6 +148,10 @@ loop.webapp = (function($, _, OT) {
|
||||
this.navigate(route, {trigger: true});
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
* Default entry point.
|
||||
*/
|
||||
@ -213,8 +219,11 @@ loop.webapp = (function($, _, OT) {
|
||||
function init() {
|
||||
var helper = new WebappHelper();
|
||||
router = new WebappRouter({
|
||||
conversation: new sharedModels.ConversationModel({}, {sdk: OT}),
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||
})
|
||||
});
|
||||
Backbone.history.start();
|
||||
if (helper.isIOS(navigator.platform)) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
[en]
|
||||
call_has_ended=Your call has ended.
|
||||
call_timeout_notification_text=Your call did not go through.
|
||||
missing_conversation_info=Missing conversation information.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
peer_ended_conversation=Your peer ended the conversation.
|
||||
@ -21,6 +22,7 @@ connection_error_see_console_notification=Call failed; see console for details.
|
||||
|
||||
[fr]
|
||||
call_has_ended=L'appel est terminé.
|
||||
call_timeout_notification_text=Votre appel n'a pas abouti.
|
||||
missing_conversation_info=Informations de communication manquantes.
|
||||
network_disconnected=La connexion réseau semble avoir été interrompue.
|
||||
peer_ended_conversation=Votre correspondant a mis fin à la communication.
|
||||
|
@ -91,7 +91,7 @@ describe("loop.Client", function() {
|
||||
it("should note the call url expiry when the request succeeds", function() {
|
||||
var callUrlData = {
|
||||
"callUrl": "fakeCallUrl",
|
||||
"expiresAt": 60
|
||||
"expiresAt": 6000
|
||||
};
|
||||
|
||||
// Sets up the hawkRequest stub to trigger the callback with no error
|
||||
@ -101,10 +101,9 @@ describe("loop.Client", function() {
|
||||
|
||||
client.requestCallUrl("foo", callback);
|
||||
|
||||
// expiresAt is in hours, and noteCallUrlExpiry wants seconds.
|
||||
sinon.assert.calledOnce(mozLoop.noteCallUrlExpiry);
|
||||
sinon.assert.calledWithExactly(mozLoop.noteCallUrlExpiry,
|
||||
60 * 60 * 60);
|
||||
6000);
|
||||
});
|
||||
|
||||
it("should send an error when the request fails", function() {
|
||||
|
@ -96,7 +96,10 @@ describe("loop.conversation", function() {
|
||||
var conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new loop.shared.models.ConversationModel({}, {sdk: {}});
|
||||
conversation = new loop.shared.models.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
sandbox.stub(conversation, "initiate");
|
||||
});
|
||||
|
||||
@ -285,7 +288,10 @@ describe("loop.conversation", function() {
|
||||
var conversation, view;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new loop.shared.models.ConversationModel({}, {sdk: {}});
|
||||
conversation = new loop.shared.models.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.conversation.IncomingCallView({model: conversation});
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@ describe("loop.shared.models", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.useFakeTimers();
|
||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
// https://github.com/cjohansen/Sinon.JS/issues/393
|
||||
@ -46,9 +47,16 @@ describe("loop.shared.models", function() {
|
||||
describe("#initialize", function() {
|
||||
it("should require a sdk option", function() {
|
||||
expect(function() {
|
||||
new sharedModels.ConversationModel();
|
||||
new sharedModels.ConversationModel({}, {});
|
||||
}).to.Throw(Error, /missing required sdk/);
|
||||
});
|
||||
|
||||
it("should accept a pendingCallTimeout option", function() {
|
||||
expect(new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
}).pendingCallTimeout).eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constructed", function() {
|
||||
@ -56,7 +64,10 @@ describe("loop.shared.models", function() {
|
||||
requestCallInfoStub, requestCallsInfoStub;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: fakeSDK});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
fakeBaseServerUrl = "http://fakeBaseServerUrl";
|
||||
fakeClient = {
|
||||
@ -68,6 +79,10 @@ describe("loop.shared.models", function() {
|
||||
});
|
||||
|
||||
describe("#initiate", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(conversation, "endSession");
|
||||
});
|
||||
|
||||
it("call requestCallInfo on the client for outgoing calls",
|
||||
function() {
|
||||
conversation.initiate({
|
||||
@ -139,6 +154,35 @@ describe("loop.shared.models", function() {
|
||||
outgoing: true
|
||||
});
|
||||
});
|
||||
|
||||
it("should end the session on outgoing call timeout", function() {
|
||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||
|
||||
conversation.initiate({
|
||||
client: fakeClient,
|
||||
outgoing: true
|
||||
});
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
|
||||
sinon.assert.calledOnce(conversation.endSession);
|
||||
});
|
||||
|
||||
it("should trigger a `timeout` event on outgoing call timeout",
|
||||
function(done) {
|
||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||
|
||||
conversation.once("timeout", function() {
|
||||
done();
|
||||
});
|
||||
|
||||
conversation.initiate({
|
||||
client: fakeClient,
|
||||
outgoing: true
|
||||
});
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setReady", function() {
|
||||
@ -161,8 +205,11 @@ describe("loop.shared.models", function() {
|
||||
var model;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(sharedModels.ConversationModel.prototype,
|
||||
"_clearPendingCallTimer");
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
@ -182,16 +229,16 @@ describe("loop.shared.models", function() {
|
||||
sinon.match.func);
|
||||
});
|
||||
|
||||
it("should set ongoing to true when no error is called back",
|
||||
it("should set connected to true when no error is called back",
|
||||
function() {
|
||||
fakeSession.connect = function(key, token, cb) {
|
||||
cb(null);
|
||||
};
|
||||
sinon.stub(model, "set");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
model.startSession();
|
||||
|
||||
sinon.assert.calledWith(model.set, "ongoing", true);
|
||||
sinon.assert.calledWith(model.set, "connected", true);
|
||||
});
|
||||
|
||||
it("should trigger session:connected when no error is called back",
|
||||
@ -215,7 +262,7 @@ describe("loop.shared.models", function() {
|
||||
error: true
|
||||
});
|
||||
};
|
||||
sinon.stub(model, "endSession");
|
||||
sandbox.stub(model, "endSession");
|
||||
|
||||
model.startSession();
|
||||
|
||||
@ -239,6 +286,17 @@ describe("loop.shared.models", function() {
|
||||
"session:connection-error", sinon.match.object);
|
||||
});
|
||||
|
||||
it("should set the connected attr to true on connection completed",
|
||||
function() {
|
||||
fakeSession.connect = function(key, token, cb) {
|
||||
cb();
|
||||
};
|
||||
|
||||
model.startSession();
|
||||
|
||||
expect(model.get("connected")).eql(true);
|
||||
});
|
||||
|
||||
it("should trigger a session:ended event on sessionDisconnected",
|
||||
function(done) {
|
||||
model.once("session:ended", function(){ done(); });
|
||||
@ -246,16 +304,32 @@ describe("loop.shared.models", function() {
|
||||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false on sessionDisconnected",
|
||||
function(done) {
|
||||
model.once("session:ended", function() {
|
||||
expect(model.get("ongoing")).eql(false);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should set the connected attribute to false on sessionDisconnected",
|
||||
function() {
|
||||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
|
||||
expect(model.get("connected")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false on sessionDisconnected",
|
||||
function() {
|
||||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
|
||||
expect(model.get("ongoing")).eql(false);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:ended", function() {
|
||||
model.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:error", function() {
|
||||
model.trigger("session:error");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
describe("connectionDestroyed event received", function() {
|
||||
var fakeEvent = {reason: "ko", connection: {connectionId: 42}};
|
||||
|
||||
@ -304,7 +378,8 @@ describe("loop.shared.models", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
@ -315,6 +390,12 @@ describe("loop.shared.models", function() {
|
||||
sinon.assert.calledOnce(fakeSession.disconnect);
|
||||
});
|
||||
|
||||
it("should set the connected attribute to false", function() {
|
||||
model.endSession();
|
||||
|
||||
expect(model.get("connected")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false", function() {
|
||||
model.endSession();
|
||||
|
||||
|
@ -102,7 +102,10 @@ describe("loop.shared.router", function() {
|
||||
});
|
||||
conversation = new loop.shared.models.ConversationModel({
|
||||
loopToken: "fakeToken"
|
||||
}, {sdk: {}});
|
||||
}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
});
|
||||
|
||||
describe("#constructor", function() {
|
||||
|
@ -205,7 +205,8 @@ describe("loop.shared.views", function() {
|
||||
initSession: sandbox.stub().returns(fakeSession)
|
||||
};
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -23,10 +23,12 @@ describe("loop.webapp", function() {
|
||||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy(),
|
||||
};
|
||||
loop.config.pendingCallTimeout = 1000;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
delete loop.config.pendingCallTimeout;
|
||||
});
|
||||
|
||||
describe("#init", function() {
|
||||
@ -69,7 +71,10 @@ describe("loop.webapp", function() {
|
||||
var router, conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
router = new loop.webapp.WebappRouter({
|
||||
conversation: conversation,
|
||||
notifier: notifier
|
||||
@ -253,7 +258,9 @@ describe("loop.webapp", function() {
|
||||
var conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000});
|
||||
});
|
||||
|
||||
describe("#initialize", function() {
|
||||
@ -268,7 +275,10 @@ describe("loop.webapp", function() {
|
||||
var conversation, initiate, view, fakeSubmitEvent;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.webapp.ConversationFormView({
|
||||
model: conversation,
|
||||
notifier: notifier
|
||||
@ -307,7 +317,10 @@ describe("loop.webapp", function() {
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({
|
||||
loopToken: "fake"
|
||||
}, {sdk: {}});
|
||||
}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.webapp.ConversationFormView({
|
||||
model: conversation,
|
||||
notifier: notifier
|
||||
|
@ -10,6 +10,7 @@ PARALLEL_DIRS += [
|
||||
'dirprovider',
|
||||
'downloads',
|
||||
'feeds',
|
||||
'loop',
|
||||
'places',
|
||||
'preferences',
|
||||
'privatebrowsing',
|
||||
@ -22,9 +23,6 @@ PARALLEL_DIRS += [
|
||||
'migration',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_LOOP']:
|
||||
PARALLEL_DIRS += ['loop']
|
||||
|
||||
DIRS += ['build']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
|
@ -1604,12 +1604,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
list-style-image: url("chrome://browser/skin/Info.png");
|
||||
}
|
||||
|
||||
%ifdef MOZ_LOOP
|
||||
/* Loop */
|
||||
#loop-call-button {
|
||||
list-style-image: url("chrome://global/skin/loop/loop-call.png");
|
||||
}
|
||||
%endif
|
||||
|
||||
/* social share panel */
|
||||
|
||||
|
@ -2226,13 +2226,11 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
}
|
||||
}
|
||||
|
||||
%ifdef MOZ_LOOP
|
||||
/* Loop */
|
||||
/* XXX - probably need retina images here */
|
||||
#loop-call-button {
|
||||
list-style-image: url("chrome://global/skin/loop/loop-call.png");
|
||||
}
|
||||
%endif
|
||||
|
||||
/* social share panel */
|
||||
.social-share-frame {
|
||||
|
@ -1540,13 +1540,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
|
||||
%ifdef MOZ_LOOP
|
||||
/* Loop */
|
||||
#loop-call-button {
|
||||
list-style-image: url("chrome://global/skin/loop/loop-call.png");
|
||||
}
|
||||
%endif
|
||||
|
||||
/* social share panel */
|
||||
|
||||
|
@ -1244,6 +1244,7 @@ X11/extensions/shape.h
|
||||
X11/extensions/scrnsaver.h
|
||||
X11/extensions/XShm.h
|
||||
X11/extensions/Xrender.h
|
||||
X11/extensions/Xfixes.h
|
||||
X11/extensions/Xdamage.h
|
||||
X11/extensions/Xcomposite.h
|
||||
X11/Intrinsic.h
|
||||
|
@ -3892,7 +3892,6 @@ LIBJPEG_TURBO_ASFLAGS=
|
||||
LIBJPEG_TURBO_X86_ASM=
|
||||
LIBJPEG_TURBO_X64_ASM=
|
||||
LIBJPEG_TURBO_ARM_ASM=
|
||||
MOZ_LOOP=$NIGHTLY_BUILD
|
||||
MOZ_PERMISSIONS=1
|
||||
MOZ_PLACES=1
|
||||
MOZ_SOCIAL=1
|
||||
@ -8177,7 +8176,7 @@ if test "$MOZ_TREE_CAIRO"; then
|
||||
MOZ_CAIRO_OSLIBS='${CAIRO_FT_OSLIBS}'
|
||||
|
||||
if test "$MOZ_X11"; then
|
||||
MOZ_CAIRO_OSLIBS="$MOZ_CAIRO_OSLIBS $XLDFLAGS -lXrender"
|
||||
MOZ_CAIRO_OSLIBS="$MOZ_CAIRO_OSLIBS $XLDFLAGS -lXext -lXdamage -lXfixes -lXcomposite -lXrender"
|
||||
fi
|
||||
|
||||
CAIRO_FEATURES_H=gfx/cairo/cairo/src/cairo-features.h
|
||||
@ -8350,12 +8349,6 @@ if test "$BUILD_CTYPES"; then
|
||||
AC_DEFINE(BUILD_CTYPES)
|
||||
fi
|
||||
|
||||
dnl Build Loop if required
|
||||
AC_SUBST(MOZ_LOOP)
|
||||
if test "$MOZ_LOOP"; then
|
||||
AC_DEFINE(MOZ_LOOP)
|
||||
fi
|
||||
|
||||
dnl Build Places if required
|
||||
if test "$MOZ_PLACES"; then
|
||||
AC_DEFINE(MOZ_PLACES)
|
||||
|
@ -17,7 +17,7 @@ interface nsIPrincipal;
|
||||
* one of these per document/principal.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(15c409c5-ebf8-457c-a8dd-5b169ca0b218)]
|
||||
[scriptable, uuid(3e923bf6-a974-4f3b-91c4-b4fd48b37732)]
|
||||
interface nsIContentSecurityPolicy : nsISerializable
|
||||
{
|
||||
|
||||
@ -221,18 +221,6 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
in ACString aMimeTypeGuess,
|
||||
in nsISupports aExtra);
|
||||
|
||||
/**
|
||||
* Delegate method called by the service when sub-elements of the protected
|
||||
* document are being processed. Given a bit of information about the request,
|
||||
* decides whether or not the policy is satisfied.
|
||||
*/
|
||||
short shouldProcess(in nsContentPolicyType aContentType,
|
||||
in nsIURI aContentLocation,
|
||||
in nsIURI aRequestOrigin,
|
||||
in nsISupports aContext,
|
||||
in ACString aMimeType,
|
||||
in nsISupports aExtra);
|
||||
|
||||
%{ C++
|
||||
// nsIObserver topic to fire when the policy encounters a violation.
|
||||
#define CSP_VIOLATION_TOPIC "csp-on-violate-policy"
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "HTMLLinkElement.h"
|
||||
#include "nsContentPolicyUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCrossSiteListenerProxy.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIChannelPolicy.h"
|
||||
#include "nsIContentPolicy.h"
|
||||
@ -59,6 +60,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportLoader)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(ImportLoader,
|
||||
mDocument,
|
||||
mImportParent,
|
||||
mLinks)
|
||||
|
||||
ImportLoader::ImportLoader(nsIURI* aURI, nsIDocument* aImportParent)
|
||||
@ -132,8 +134,8 @@ public:
|
||||
mNode,
|
||||
mSuccess ? NS_LITERAL_STRING("load")
|
||||
: NS_LITERAL_STRING("error"),
|
||||
/* aCanBubble = */ true,
|
||||
/* aCancelable = */ true);
|
||||
/* aCanBubble = */ false,
|
||||
/* aCancelable = */ false);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -177,7 +179,6 @@ ImportLoader::Error(bool aUnblockScripts)
|
||||
if (aUnblockScripts) {
|
||||
UnblockScripts();
|
||||
}
|
||||
|
||||
ReleaseResources();
|
||||
}
|
||||
|
||||
@ -186,7 +187,6 @@ ImportLoader::Error(bool aUnblockScripts)
|
||||
void ImportLoader::ReleaseResources()
|
||||
{
|
||||
mParserStreamListener = nullptr;
|
||||
mChannel = nullptr;
|
||||
mImportParent = nullptr;
|
||||
}
|
||||
|
||||
@ -198,6 +198,7 @@ ImportLoader::Open()
|
||||
nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
|
||||
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(master);
|
||||
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
||||
|
||||
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
|
||||
mURI,
|
||||
@ -213,6 +214,11 @@ ImportLoader::Open()
|
||||
return;
|
||||
}
|
||||
|
||||
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
||||
rv = secMan->CheckLoadURIWithPrincipal(principal, mURI,
|
||||
nsIScriptSecurityManager::STANDARD);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
nsCOMPtr<nsILoadGroup> loadGroup = mImportParent->GetDocumentLoadGroup();
|
||||
nsCOMPtr<nsIChannelPolicy> channelPolicy;
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
||||
@ -224,7 +230,8 @@ ImportLoader::Open()
|
||||
channelPolicy->SetContentSecurityPolicy(csp);
|
||||
channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SUBDOCUMENT);
|
||||
}
|
||||
rv = NS_NewChannel(getter_AddRefs(mChannel),
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
rv = NS_NewChannel(getter_AddRefs(channel),
|
||||
mURI,
|
||||
/* ioService = */ nullptr,
|
||||
loadGroup,
|
||||
@ -233,7 +240,16 @@ ImportLoader::Open()
|
||||
channelPolicy);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
mChannel->AsyncOpen(this, nullptr);
|
||||
// Init CORSListenerProxy and omit credentials.
|
||||
nsRefPtr<nsCORSListenerProxy> corsListener =
|
||||
new nsCORSListenerProxy(this, principal,
|
||||
/* aWithCredentials */ false);
|
||||
rv = corsListener->Init(channel, true);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
rv = channel->AsyncOpen(corsListener, nullptr);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
BlockScripts();
|
||||
ae.Pass();
|
||||
}
|
||||
@ -248,9 +264,13 @@ ImportLoader::OnDataAvailable(nsIRequest* aRequest,
|
||||
MOZ_ASSERT(mParserStreamListener);
|
||||
|
||||
AutoError ae(this);
|
||||
nsresult rv = mParserStreamListener->OnDataAvailable(mChannel, aContext,
|
||||
aStream, aOffset,
|
||||
aCount);
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mParserStreamListener->OnDataAvailable(channel, aContext,
|
||||
aStream, aOffset,
|
||||
aCount);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
ae.Pass();
|
||||
return rv;
|
||||
@ -278,17 +298,20 @@ ImportLoader::OnStopRequest(nsIRequest* aRequest,
|
||||
if (aStatus == NS_ERROR_DOM_ABORT_ERR) {
|
||||
// We failed in OnStartRequest, nothing more to do (we've already
|
||||
// dispatched an error event) just return here.
|
||||
MOZ_ASSERT(!mChannel);
|
||||
MOZ_ASSERT(mStopped);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aRequest == mChannel,
|
||||
"Wrong channel something went horribly wrong");
|
||||
|
||||
if (mParserStreamListener) {
|
||||
mParserStreamListener->OnStopRequest(aRequest, aContext, aStatus);
|
||||
}
|
||||
|
||||
if (!mDocument) {
|
||||
// If at this point we don't have a document, then the error was
|
||||
// already reported.
|
||||
return NS_ERROR_DOM_ABORT_ERR;
|
||||
}
|
||||
|
||||
nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mDocument);
|
||||
EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
|
||||
manager->AddEventListenerByType(this,
|
||||
@ -300,16 +323,31 @@ ImportLoader::OnStopRequest(nsIRequest* aRequest,
|
||||
NS_IMETHODIMP
|
||||
ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
{
|
||||
MOZ_ASSERT(aRequest == mChannel,
|
||||
"Wrong channel, something went horribly wrong");
|
||||
|
||||
AutoError ae(this);
|
||||
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mImportParent);
|
||||
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
||||
mChannel->SetOwner(principal);
|
||||
if (!sop) {
|
||||
return NS_ERROR_DOM_ABORT_ERR;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
||||
if (!channel) {
|
||||
return NS_ERROR_DOM_ABORT_ERR;
|
||||
}
|
||||
|
||||
if (nsContentUtils::IsSystemPrincipal(principal)) {
|
||||
// We should never import non-system documents and run their scripts with system principal!
|
||||
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
||||
nsContentUtils::GetSecurityManager()->GetChannelPrincipal(channel,
|
||||
getter_AddRefs(channelPrincipal));
|
||||
if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
channel->SetOwner(principal);
|
||||
|
||||
nsAutoCString type;
|
||||
mChannel->GetContentType(type);
|
||||
channel->GetContentType(type);
|
||||
if (!type.EqualsLiteral("text/html")) {
|
||||
NS_WARNING("ImportLoader wrong content type");
|
||||
return NS_ERROR_DOM_ABORT_ERR;
|
||||
@ -332,11 +370,15 @@ ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
|
||||
mDocument->SetMasterDocument(master);
|
||||
|
||||
// We have to connect the blank document we created with the channel we opened.
|
||||
// We have to connect the blank document we created with the channel we opened,
|
||||
// and create its own LoadGroup for it.
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
nsCOMPtr<nsILoadGroup> loadGroup;
|
||||
mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
|
||||
rv = mDocument->StartDocumentLoad("import", mChannel, loadGroup,
|
||||
channel->GetLoadGroup(getter_AddRefs(loadGroup));
|
||||
nsCOMPtr<nsILoadGroup> newLoadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
|
||||
NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
|
||||
newLoadGroup->SetLoadGroup(loadGroup);
|
||||
rv = mDocument->StartDocumentLoad("import", channel, newLoadGroup,
|
||||
nullptr, getter_AddRefs(listener),
|
||||
true);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
|
||||
|
@ -117,7 +117,6 @@ private:
|
||||
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsCOMPtr<nsIStreamListener> mParserStreamListener;
|
||||
nsCOMPtr<nsIDocument> mImportParent;
|
||||
// List of the LinkElements that are referring to this import
|
||||
|
@ -209,19 +209,6 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::ShouldProcess(nsContentPolicyType aContentType,
|
||||
nsIURI* aContentLocation,
|
||||
nsIURI* aRequestOrigin,
|
||||
nsISupports* aRequestContext,
|
||||
const nsACString& aMimeType,
|
||||
nsISupports* aExtra,
|
||||
int16_t* outDecision)
|
||||
{
|
||||
*outDecision = nsIContentPolicy::ACCEPT;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* ===== nsISupports implementation ========== */
|
||||
|
||||
NS_IMPL_CLASSINFO(nsCSPContext,
|
||||
|
@ -218,56 +218,7 @@ CSPService::ShouldProcess(uint32_t aContentType,
|
||||
if (!aContentLocation)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// default decision is to accept the item
|
||||
*aDecision = nsIContentPolicy::ACCEPT;
|
||||
|
||||
// No need to continue processing if CSP is disabled
|
||||
if (!sCSPEnabled)
|
||||
return NS_OK;
|
||||
|
||||
// find the nsDocument that initiated this request and see if it has a
|
||||
// CSP policy object
|
||||
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
||||
if (node) {
|
||||
principal = node->NodePrincipal();
|
||||
principal->GetCsp(getter_AddRefs(csp));
|
||||
|
||||
if (csp) {
|
||||
#ifdef PR_LOGGING
|
||||
{
|
||||
uint32_t numPolicies = 0;
|
||||
nsresult rv = csp->GetPolicyCount(&numPolicies);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
for (uint32_t i=0; i<numPolicies; i++) {
|
||||
nsAutoString policy;
|
||||
csp->GetPolicy(i, policy);
|
||||
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||
("shouldProcess - document has policy[%d]: %s", i,
|
||||
NS_ConvertUTF16toUTF8(policy).get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// obtain the enforcement decision
|
||||
csp->ShouldProcess(aContentType,
|
||||
aContentLocation,
|
||||
aRequestOrigin,
|
||||
aRequestContext,
|
||||
aMimeTypeGuess,
|
||||
aExtra,
|
||||
aDecision);
|
||||
}
|
||||
}
|
||||
#ifdef PR_LOGGING
|
||||
else {
|
||||
nsAutoCString uriSpec;
|
||||
aContentLocation->GetSpec(uriSpec);
|
||||
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||
("COULD NOT get nsINode for location: %s", uriSpec.get()));
|
||||
}
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
5
content/html/content/test/file_imports_redirect.html
Normal file
5
content/html/content/test/file_imports_redirect.html
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,2 @@
|
||||
HTTP 301 Moved Permanently
|
||||
Location: http://mochi.test:8888/tests/content/html/content/test/file_imports_redirected.html
|
6
content/html/content/test/file_imports_redirected.html
Normal file
6
content/html/content/test/file_imports_redirected.html
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script> var redirected = true; </script>
|
||||
</body>
|
||||
</html>
|
@ -148,6 +148,9 @@ support-files =
|
||||
file_iframe_sandbox_window_top_navigation_fail.html
|
||||
file_iframe_sandbox_worker.js
|
||||
file_imports_basics.html
|
||||
file_imports_redirect.html
|
||||
file_imports_redirect.html^headers^
|
||||
file_imports_redirected.html
|
||||
file_srcdoc-2.html
|
||||
file_srcdoc.html
|
||||
form_submit_server.sjs
|
||||
@ -454,6 +457,8 @@ skip-if = buildapp == 'b2g' || e10s # b2g(multiple concurrent window.open()s fai
|
||||
[test_img_attributes_reflection.html]
|
||||
[test_imageSrcSet.html]
|
||||
[test_imports_basics.html]
|
||||
[test_imports_redirect.html]
|
||||
[test_imports_nonhttp.html]
|
||||
[test_li_attributes_reflection.html]
|
||||
[test_link_attributes_reflection.html]
|
||||
[test_link_sizes.html]
|
||||
|
56
content/html/content/test/test_imports_nonhttp.html
Normal file
56
content/html/content/test/test_imports_nonhttp.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1016875
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1016875</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1016875">Mozilla Bug 1016875</a>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var counter = 0;
|
||||
function loaded() {
|
||||
if ( counter++ == 1 ) {
|
||||
ok(document.getElementById("import1").import.getElementById("div1"),
|
||||
"import document was loaded");
|
||||
ok(document.getElementById("import2").import.getElementById("div1"),
|
||||
"import document was loaded");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
function failed() {
|
||||
ok(false, "Import should not have failed")
|
||||
SimpleTest.finish();
|
||||
}
|
||||
var link = document.createElement("link");
|
||||
link.setAttribute("id", "import1");
|
||||
link.setAttribute("rel", "import");
|
||||
var stringDoc = "<!DOCTYPE html><html><body><div id='div1'></div></body></html>";
|
||||
var encoded = btoa(stringDoc);
|
||||
var dataurl = "data:text/html;base64," + encoded;
|
||||
link.setAttribute("href", dataurl);
|
||||
link.onload = loaded;
|
||||
link.onerror = failed;
|
||||
document.body.appendChild(link);
|
||||
|
||||
var link = document.createElement("link");
|
||||
link.setAttribute("id", "import2");
|
||||
link.setAttribute("rel", "import");
|
||||
var blob = new Blob([stringDoc], {type : 'text/html'});
|
||||
var objectURL = URL.createObjectURL(blob);
|
||||
link.setAttribute("href", objectURL);
|
||||
link.onload = loaded;
|
||||
link.onerror = failed;
|
||||
document.body.appendChild(link);
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
39
content/html/content/test/test_imports_redirect.html
Normal file
39
content/html/content/test/test_imports_redirect.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1016875
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1016875</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1016875">Mozilla Bug 1016875</a>
|
||||
|
||||
<script type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var loadEventFired = false;
|
||||
var errorEventFired = false;
|
||||
var counter = 0;
|
||||
function loaded() {
|
||||
loadEventFired = true;
|
||||
}
|
||||
function failed() {
|
||||
errorEventFired = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<link rel="import" href="http://mochi.test:8888/tests/content/html/content/test/file_imports_redirect.html" id="import" onload="loaded()" onerror="failed()"></link>
|
||||
|
||||
<script type="text/javascript">
|
||||
ok(loadEventFired, "Load event was fired");
|
||||
ok(!errorEventFired, "Error event was not fired, redirection worked");
|
||||
ok(redirected, "Script of the target of redirection was executed");
|
||||
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -405,7 +405,6 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' # Disabled on Android & B2G
|
||||
[test_played.html]
|
||||
skip-if = true # bug 1021794
|
||||
[test_preload_actions.html]
|
||||
skip-if = toolkit == 'android' # bug 886188
|
||||
[test_preload_attribute.html]
|
||||
[test_preload_suspend.html]
|
||||
skip-if = true # bug 493692
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "nsIDOMFile.h"
|
||||
#include "DOMMediaStream.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -54,11 +55,13 @@ public:
|
||||
|
||||
/* Populate an array of video sources in the nsTArray. Also include devices
|
||||
* that are currently unavailable. */
|
||||
virtual void EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >*) = 0;
|
||||
virtual void EnumerateVideoDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineVideoSource> >*) = 0;
|
||||
|
||||
/* Populate an array of audio sources in the nsTArray. Also include devices
|
||||
* that are currently unavailable. */
|
||||
virtual void EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >*) = 0;
|
||||
virtual void EnumerateAudioDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineAudioSource> >*) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~MediaEngine() {}
|
||||
@ -182,6 +185,9 @@ class MediaEngineVideoSource : public MediaEngineSource
|
||||
public:
|
||||
virtual ~MediaEngineVideoSource() {}
|
||||
|
||||
virtual const dom::MediaSourceEnum GetMediaSource() {
|
||||
return dom::MediaSourceEnum::Camera;
|
||||
}
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) = 0;
|
||||
|
@ -478,9 +478,15 @@ MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineDefault::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources) {
|
||||
MediaEngineDefault::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||
nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// only supports camera sources (for now). See Bug 1038241
|
||||
if (aMediaSource != dom::MediaSourceEnum::Camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We once had code here to find a VideoSource with the same settings and re-use that.
|
||||
// This no longer is possible since the resolution is being set in Allocate().
|
||||
|
||||
@ -492,10 +498,13 @@ MediaEngineDefault::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSour
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineDefault::EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources) {
|
||||
MediaEngineDefault::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
|
||||
nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
int32_t len = mASources.Length();
|
||||
|
||||
// aMediaSource is ignored for audio devices (for now).
|
||||
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
nsRefPtr<MediaEngineAudioSource> source = mASources.ElementAt(i);
|
||||
if (source->IsAvailable()) {
|
||||
|
@ -138,8 +138,10 @@ public:
|
||||
: mMutex("mozilla::MediaEngineDefault")
|
||||
{}
|
||||
|
||||
virtual void EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >*);
|
||||
virtual void EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >*);
|
||||
virtual void EnumerateVideoDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineVideoSource> >*);
|
||||
virtual void EnumerateAudioDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineAudioSource> >*);
|
||||
|
||||
private:
|
||||
~MediaEngineDefault() {}
|
||||
|
@ -45,12 +45,15 @@ GetUserMediaLog()
|
||||
namespace mozilla {
|
||||
|
||||
MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
|
||||
: mMutex("mozilla::MediaEngineWebRTC")
|
||||
, mVideoEngine(nullptr)
|
||||
, mVoiceEngine(nullptr)
|
||||
, mVideoEngineInit(false)
|
||||
, mAudioEngineInit(false)
|
||||
, mHasTabVideoSource(false)
|
||||
: mMutex("mozilla::MediaEngineWebRTC")
|
||||
, mScreenEngine(nullptr)
|
||||
, mAppEngine(nullptr)
|
||||
, mVideoEngine(nullptr)
|
||||
, mVoiceEngine(nullptr)
|
||||
, mVideoEngineInit(false)
|
||||
, mAudioEngineInit(false)
|
||||
, mScreenEngineInit(false)
|
||||
, mAppEngineInit(false)
|
||||
{
|
||||
#ifndef MOZ_B2G_CAMERA
|
||||
nsCOMPtr<nsIComponentRegistrar> compMgr;
|
||||
@ -69,11 +72,18 @@ MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources)
|
||||
MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||
nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources)
|
||||
{
|
||||
#ifdef MOZ_B2G_CAMERA
|
||||
// We spawn threads to handle gUM runnables, so we must protect the member vars
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
#ifdef MOZ_B2G_CAMERA
|
||||
if (aMediaSource != dom::MediaSourceEnum::Camera) {
|
||||
// only supports camera sources
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We still enumerate every time, in case a new device was plugged in since
|
||||
* the last call. TODO: Verify that WebRTC actually does deal with hotplugging
|
||||
@ -102,7 +112,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSourc
|
||||
// We've already seen this device, just append.
|
||||
aVSources->AppendElement(vSource.get());
|
||||
} else {
|
||||
vSource = new MediaEngineWebRTCVideoSource(i);
|
||||
vSource = new MediaEngineWebRTCVideoSource(i, aMediaSource);
|
||||
mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
|
||||
aVSources->AppendElement(vSource);
|
||||
}
|
||||
@ -112,9 +122,9 @@ MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSourc
|
||||
#else
|
||||
ScopedCustomReleasePtr<webrtc::ViEBase> ptrViEBase;
|
||||
ScopedCustomReleasePtr<webrtc::ViECapture> ptrViECapture;
|
||||
|
||||
// We spawn threads to handle gUM runnables, so we must protect the member vars
|
||||
MutexAutoLock lock(mMutex);
|
||||
webrtc::Config configSet;
|
||||
webrtc::VideoEngine *videoEngine = nullptr;
|
||||
bool *videoEngineInit = nullptr;
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// get the JVM
|
||||
@ -125,25 +135,53 @@ MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSourc
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (!mVideoEngine) {
|
||||
if (!(mVideoEngine = webrtc::VideoEngine::Create())) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aMediaSource) {
|
||||
case dom::MediaSourceEnum::Application:
|
||||
mAppEngineConfig.Set<webrtc::CaptureDeviceInfo>(
|
||||
new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Application));
|
||||
if (!mAppEngine) {
|
||||
if (!(mAppEngine = webrtc::VideoEngine::Create(mAppEngineConfig))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
videoEngine = mAppEngine;
|
||||
videoEngineInit = &mAppEngineInit;
|
||||
break;
|
||||
case dom::MediaSourceEnum::Screen:
|
||||
mScreenEngineConfig.Set<webrtc::CaptureDeviceInfo>(
|
||||
new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Screen));
|
||||
if (!mScreenEngine) {
|
||||
if (!(mScreenEngine = webrtc::VideoEngine::Create(mScreenEngineConfig))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
videoEngine = mScreenEngine;
|
||||
videoEngineInit = &mScreenEngineInit;
|
||||
break;
|
||||
case dom::MediaSourceEnum::Camera:
|
||||
// fall through
|
||||
default:
|
||||
if (!mVideoEngine) {
|
||||
if (!(mVideoEngine = webrtc::VideoEngine::Create())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
videoEngine = mVideoEngine;
|
||||
videoEngineInit = &mVideoEngineInit;
|
||||
break;
|
||||
}
|
||||
|
||||
ptrViEBase = webrtc::ViEBase::GetInterface(mVideoEngine);
|
||||
ptrViEBase = webrtc::ViEBase::GetInterface(videoEngine);
|
||||
if (!ptrViEBase) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mVideoEngineInit) {
|
||||
if (ptrViEBase->Init() < 0) {
|
||||
return;
|
||||
}
|
||||
mVideoEngineInit = true;
|
||||
if (ptrViEBase->Init() < 0) {
|
||||
return;
|
||||
}
|
||||
*videoEngineInit = true;
|
||||
|
||||
ptrViECapture = webrtc::ViECapture::GetInterface(mVideoEngine);
|
||||
ptrViECapture = webrtc::ViECapture::GetInterface(videoEngine);
|
||||
if (!ptrViECapture) {
|
||||
return;
|
||||
}
|
||||
@ -207,7 +245,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSourc
|
||||
// We've already seen this device, just append.
|
||||
aVSources->AppendElement(vSource.get());
|
||||
} else {
|
||||
vSource = new MediaEngineWebRTCVideoSource(mVideoEngine, i);
|
||||
vSource = new MediaEngineWebRTCVideoSource(videoEngine, i, aMediaSource);
|
||||
mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
|
||||
aVSources->AppendElement(vSource);
|
||||
}
|
||||
@ -221,7 +259,8 @@ MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSourc
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineWebRTC::EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources)
|
||||
MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
|
||||
nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources)
|
||||
{
|
||||
ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
|
||||
ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
|
||||
@ -323,6 +362,13 @@ MediaEngineWebRTC::Shutdown()
|
||||
webrtc::VideoEngine::Delete(mVideoEngine);
|
||||
}
|
||||
|
||||
if (mScreenEngine) {
|
||||
webrtc::VideoEngine::Delete(mScreenEngine);
|
||||
}
|
||||
if (mAppEngine) {
|
||||
webrtc::VideoEngine::Delete(mAppEngine);
|
||||
}
|
||||
|
||||
if (mVoiceEngine) {
|
||||
mAudioSources.Clear();
|
||||
mVoiceEngine->SetTraceCallback(nullptr);
|
||||
@ -331,6 +377,8 @@ MediaEngineWebRTC::Shutdown()
|
||||
|
||||
mVideoEngine = nullptr;
|
||||
mVoiceEngine = nullptr;
|
||||
mScreenEngine = nullptr;
|
||||
mAppEngine = nullptr;
|
||||
|
||||
if (mThread) {
|
||||
mThread->Shutdown();
|
||||
|
@ -28,9 +28,9 @@
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
#include "MediaEngineWrapper.h"
|
||||
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
// WebRTC library includes follow
|
||||
|
||||
#include "webrtc/common.h"
|
||||
// Audio Engine
|
||||
#include "webrtc/voice_engine/include/voe_base.h"
|
||||
#include "webrtc/voice_engine/include/voe_codec.h"
|
||||
@ -95,12 +95,14 @@ class MediaEngineWebRTCVideoSource : public MediaEngineVideoSource
|
||||
{
|
||||
public:
|
||||
#ifdef MOZ_B2G_CAMERA
|
||||
MediaEngineWebRTCVideoSource(int aIndex)
|
||||
MediaEngineWebRTCVideoSource(int aIndex,
|
||||
dom::MediaSourceEnum aMediaSource = dom::MediaSourceEnum::Camera)
|
||||
: mCameraControl(nullptr)
|
||||
, mCallbackMonitor("WebRTCCamera.CallbackMonitor")
|
||||
, mRotation(0)
|
||||
, mBackCamera(false)
|
||||
, mCaptureIndex(aIndex)
|
||||
, mMediaSource(aMediaSource)
|
||||
, mMonitor("WebRTCCamera.Monitor")
|
||||
, mWidth(0)
|
||||
, mHeight(0)
|
||||
@ -124,11 +126,13 @@ public:
|
||||
*/
|
||||
virtual bool IsTextureSupported() { return false; }
|
||||
|
||||
MediaEngineWebRTCVideoSource(webrtc::VideoEngine* aVideoEnginePtr, int aIndex)
|
||||
MediaEngineWebRTCVideoSource(webrtc::VideoEngine* aVideoEnginePtr, int aIndex,
|
||||
dom::MediaSourceEnum aMediaSource = dom::MediaSourceEnum::Camera)
|
||||
: mVideoEngine(aVideoEnginePtr)
|
||||
, mCaptureIndex(aIndex)
|
||||
, mFps(-1)
|
||||
, mMinFps(-1)
|
||||
, mMediaSource(aMediaSource)
|
||||
, mMonitor("WebRTCCamera.Monitor")
|
||||
, mWidth(0)
|
||||
, mHeight(0)
|
||||
@ -165,6 +169,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const dom::MediaSourceEnum GetMediaSource() {
|
||||
return mMediaSource;
|
||||
}
|
||||
|
||||
#ifndef MOZ_B2G_CAMERA
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
#else
|
||||
@ -239,6 +247,7 @@ private:
|
||||
int mCaptureIndex;
|
||||
int mFps; // Track rate (30 fps by default)
|
||||
int mMinFps; // Min rate we want to accept
|
||||
dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
|
||||
|
||||
// mMonitor protects mImage access/changes, and transitions of mState
|
||||
// from kStarted to kStopped (which are combined with EndTrack() and
|
||||
@ -381,9 +390,10 @@ public:
|
||||
// before invoking Shutdown on this class.
|
||||
void Shutdown();
|
||||
|
||||
virtual void EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >*);
|
||||
virtual void EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >*);
|
||||
|
||||
virtual void EnumerateVideoDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineVideoSource> >*);
|
||||
virtual void EnumerateAudioDevices(dom::MediaSourceEnum,
|
||||
nsTArray<nsRefPtr<MediaEngineAudioSource> >*);
|
||||
private:
|
||||
~MediaEngineWebRTC() {
|
||||
Shutdown();
|
||||
@ -397,14 +407,22 @@ private:
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
|
||||
Mutex mMutex;
|
||||
// protected with mMutex:
|
||||
|
||||
// protected with mMutex:
|
||||
webrtc::VideoEngine* mScreenEngine;
|
||||
webrtc::VideoEngine* mAppEngine;
|
||||
webrtc::VideoEngine* mVideoEngine;
|
||||
webrtc::VoiceEngine* mVoiceEngine;
|
||||
|
||||
// specialized configurations
|
||||
webrtc::Config mAppEngineConfig;
|
||||
webrtc::Config mScreenEngineConfig;
|
||||
|
||||
// Need this to avoid unneccesary WebRTC calls while enumerating.
|
||||
bool mVideoEngineInit;
|
||||
bool mAudioEngineInit;
|
||||
bool mScreenEngineInit;
|
||||
bool mAppEngineInit;
|
||||
bool mHasTabVideoSource;
|
||||
|
||||
// Store devices we've already seen in a hashtable for quick return.
|
||||
|
@ -42,6 +42,19 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// treat MediaSource special because it's always required
|
||||
mRequired.mMediaSource = mMediaSource;
|
||||
|
||||
if (mMediaSource != dom::MediaSourceEnum::Camera && mAdvanced.WasPassed()) {
|
||||
// iterate through advanced, forcing mediaSource to match "root"
|
||||
auto& array = mAdvanced.Value();
|
||||
for (uint32_t i = 0; i < array.Length(); i++) {
|
||||
if (array[i].mMediaSource == dom::MediaSourceEnum::Camera) {
|
||||
array[i].mMediaSource = mMediaSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected:
|
||||
MediaTrackConstraintSet& Triage(const Kind kind) {
|
||||
@ -85,6 +98,9 @@ struct VideoTrackConstraintsN :
|
||||
Triage(Kind::Width).mWidth = mWidth;
|
||||
Triage(Kind::Height).mHeight = mHeight;
|
||||
Triage(Kind::FrameRate).mFrameRate = mFrameRate;
|
||||
|
||||
// treat MediaSource special because it's always required
|
||||
mRequired.mMediaSource = mMediaSource;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -185,10 +185,12 @@ PostMessageReadTransferStructuredClone(JSContext* aCx,
|
||||
scInfo->mPorts.Put(port, nullptr);
|
||||
|
||||
JS::Rooted<JSObject*> obj(aCx, port->WrapObject(aCx));
|
||||
if (JS_WrapObject(aCx, &obj)) {
|
||||
MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner());
|
||||
returnObject.set(obj);
|
||||
if (!obj || !JS_WrapObject(aCx, &obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner());
|
||||
returnObject.set(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3017,6 +3017,14 @@ nsJSContext::EnsureStatics()
|
||||
Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
|
||||
"dom.cycle_collector.incremental");
|
||||
|
||||
Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
|
||||
"javascript.options.mem.gc_min_empty_chunk_count",
|
||||
(void *)JSGC_MIN_EMPTY_CHUNK_COUNT);
|
||||
|
||||
Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
|
||||
"javascript.options.mem.gc_max_empty_chunk_count",
|
||||
(void *)JSGC_MAX_EMPTY_CHUNK_COUNT);
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (!obs) {
|
||||
MOZ_CRASH();
|
||||
|
@ -4,9 +4,14 @@
|
||||
* 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 <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include "base/message_loop.h"
|
||||
#include "BluetoothInterface.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
@ -69,6 +74,40 @@ private:
|
||||
Arg1 mArg1;
|
||||
};
|
||||
|
||||
template <typename Obj, typename Res,
|
||||
typename Arg1, typename Arg2, typename Arg3>
|
||||
class BluetoothInterfaceRunnable3 : public nsRunnable
|
||||
{
|
||||
public:
|
||||
BluetoothInterfaceRunnable3(Obj* aObj,
|
||||
Res (Obj::*aMethod)(Arg1, Arg2, Arg3),
|
||||
const Arg1& aArg1, const Arg2& aArg2,
|
||||
const Arg3& aArg3)
|
||||
: mObj(aObj)
|
||||
, mMethod(aMethod)
|
||||
, mArg1(aArg1)
|
||||
, mArg2(aArg2)
|
||||
, mArg3(aArg3)
|
||||
{
|
||||
MOZ_ASSERT(mObj);
|
||||
MOZ_ASSERT(mMethod);
|
||||
}
|
||||
|
||||
NS_METHOD
|
||||
Run() MOZ_OVERRIDE
|
||||
{
|
||||
((*mObj).*mMethod)(mArg1, mArg2, mArg3);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<Obj> mObj;
|
||||
void (Obj::*mMethod)(Arg1, Arg2, Arg3);
|
||||
Arg1 mArg1;
|
||||
Arg2 mArg2;
|
||||
Arg3 mArg3;
|
||||
};
|
||||
|
||||
//
|
||||
// Socket Interface
|
||||
//
|
||||
@ -84,23 +123,432 @@ struct interface_traits<BluetoothSocketInterface>
|
||||
}
|
||||
};
|
||||
|
||||
bt_status_t
|
||||
BluetoothSocketInterface::Listen(btsock_type_t aType,
|
||||
const char* aServiceName,
|
||||
const uint8_t* aServiceUuid, int aChannel,
|
||||
int& aSockFd, int aFlags)
|
||||
typedef
|
||||
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, int>
|
||||
BluetoothSocketIntResultRunnable;
|
||||
|
||||
typedef
|
||||
BluetoothInterfaceRunnable3<BluetoothSocketResultHandler,
|
||||
void, int, const nsAString_internal&, int>
|
||||
BluetoothSocketIntStringIntResultRunnable;
|
||||
|
||||
typedef
|
||||
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, bt_status_t>
|
||||
BluetoothSocketErrorRunnable;
|
||||
|
||||
static nsresult
|
||||
DispatchBluetoothSocketResult(BluetoothSocketResultHandler* aRes,
|
||||
void (BluetoothSocketResultHandler::*aMethod)(int),
|
||||
int aArg, bt_status_t aStatus)
|
||||
{
|
||||
return mInterface->listen(aType, aServiceName, aServiceUuid, aChannel,
|
||||
&aSockFd, aFlags);
|
||||
MOZ_ASSERT(aRes);
|
||||
|
||||
nsRunnable* runnable;
|
||||
|
||||
if (aStatus == BT_STATUS_SUCCESS) {
|
||||
runnable = new BluetoothSocketIntResultRunnable(aRes, aMethod, aArg);
|
||||
} else {
|
||||
runnable = new BluetoothSocketErrorRunnable(aRes,
|
||||
&BluetoothSocketResultHandler::OnError, aStatus);
|
||||
}
|
||||
nsresult rv = NS_DispatchToMainThread(runnable);
|
||||
if (NS_FAILED(rv)) {
|
||||
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bt_status_t
|
||||
static nsresult
|
||||
DispatchBluetoothSocketResult(
|
||||
BluetoothSocketResultHandler* aRes,
|
||||
void (BluetoothSocketResultHandler::*aMethod)(int, const nsAString&, int),
|
||||
int aArg1, const nsAString& aArg2, int aArg3, bt_status_t aStatus)
|
||||
{
|
||||
MOZ_ASSERT(aRes);
|
||||
|
||||
nsRunnable* runnable;
|
||||
|
||||
if (aStatus == BT_STATUS_SUCCESS) {
|
||||
runnable = new BluetoothSocketIntStringIntResultRunnable(aRes, aMethod,
|
||||
aArg1, aArg2,
|
||||
aArg3);
|
||||
} else {
|
||||
runnable = new BluetoothSocketErrorRunnable(aRes,
|
||||
&BluetoothSocketResultHandler::OnError, aStatus);
|
||||
}
|
||||
nsresult rv = NS_DispatchToMainThread(runnable);
|
||||
if (NS_FAILED(rv)) {
|
||||
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocketInterface::Listen(btsock_type_t aType,
|
||||
const char* aServiceName,
|
||||
const uint8_t* aServiceUuid,
|
||||
int aChannel, int aFlags,
|
||||
BluetoothSocketResultHandler* aRes)
|
||||
{
|
||||
int fd;
|
||||
|
||||
bt_status_t status = mInterface->listen(aType, aServiceName, aServiceUuid,
|
||||
aChannel, &fd, aFlags);
|
||||
if (aRes) {
|
||||
DispatchBluetoothSocketResult(aRes, &BluetoothSocketResultHandler::Listen,
|
||||
fd, status);
|
||||
}
|
||||
}
|
||||
|
||||
#define CMSGHDR_CONTAINS_FD(_cmsghdr) \
|
||||
( ((_cmsghdr)->cmsg_level == SOL_SOCKET) && \
|
||||
((_cmsghdr)->cmsg_type == SCM_RIGHTS) )
|
||||
|
||||
/* |SocketMessageWatcher| receives Bluedroid's socket setup
|
||||
* messages on the I/O thread. You need to inherit from this
|
||||
* class to make use of it.
|
||||
*
|
||||
* Bluedroid sends two socket info messages (20 bytes) at
|
||||
* the beginning of a connection to both peers.
|
||||
*
|
||||
* - 1st message: [channel:4]
|
||||
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
|
||||
*
|
||||
* On the server side, the second message will contain a
|
||||
* socket file descriptor for the connection. The client
|
||||
* uses the original file descriptor.
|
||||
*/
|
||||
class SocketMessageWatcher : public MessageLoopForIO::Watcher
|
||||
{
|
||||
public:
|
||||
static const unsigned char MSG1_SIZE = 4;
|
||||
static const unsigned char MSG2_SIZE = 16;
|
||||
|
||||
static const unsigned char OFF_CHANNEL1 = 0;
|
||||
static const unsigned char OFF_SIZE = 4;
|
||||
static const unsigned char OFF_BDADDRESS = 6;
|
||||
static const unsigned char OFF_CHANNEL2 = 12;
|
||||
static const unsigned char OFF_STATUS = 16;
|
||||
|
||||
SocketMessageWatcher(int aFd)
|
||||
: mFd(aFd)
|
||||
, mClientFd(-1)
|
||||
, mLen(0)
|
||||
{ }
|
||||
|
||||
virtual ~SocketMessageWatcher()
|
||||
{ }
|
||||
|
||||
virtual void Proceed(bt_status_t aStatus) = 0;
|
||||
|
||||
void OnFileCanReadWithoutBlocking(int aFd) MOZ_OVERRIDE
|
||||
{
|
||||
bt_status_t status;
|
||||
|
||||
switch (mLen) {
|
||||
case 0:
|
||||
status = RecvMsg1();
|
||||
break;
|
||||
case MSG1_SIZE:
|
||||
status = RecvMsg2();
|
||||
break;
|
||||
default:
|
||||
/* message-size error */
|
||||
status = BT_STATUS_FAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsComplete() || status != BT_STATUS_SUCCESS) {
|
||||
mWatcher.StopWatchingFileDescriptor();
|
||||
Proceed(status);
|
||||
}
|
||||
}
|
||||
|
||||
void OnFileCanWriteWithoutBlocking(int aFd) MOZ_OVERRIDE
|
||||
{ }
|
||||
|
||||
void Watch()
|
||||
{
|
||||
MessageLoopForIO::current()->WatchFileDescriptor(
|
||||
mFd,
|
||||
true,
|
||||
MessageLoopForIO::WATCH_READ,
|
||||
&mWatcher,
|
||||
this);
|
||||
}
|
||||
|
||||
bool IsComplete() const
|
||||
{
|
||||
return mLen == (MSG1_SIZE + MSG2_SIZE);
|
||||
}
|
||||
|
||||
int GetFd() const
|
||||
{
|
||||
return mFd;
|
||||
}
|
||||
|
||||
int32_t GetChannel1() const
|
||||
{
|
||||
return ReadInt32(OFF_CHANNEL1);
|
||||
}
|
||||
|
||||
int32_t GetSize() const
|
||||
{
|
||||
return ReadInt16(OFF_SIZE);
|
||||
}
|
||||
|
||||
nsString GetBdAddress() const
|
||||
{
|
||||
nsString bdAddress;
|
||||
ReadBdAddress(OFF_BDADDRESS, bdAddress);
|
||||
return bdAddress;
|
||||
}
|
||||
|
||||
int32_t GetChannel2() const
|
||||
{
|
||||
return ReadInt32(OFF_CHANNEL2);
|
||||
}
|
||||
|
||||
int32_t GetConnectionStatus() const
|
||||
{
|
||||
return ReadInt32(OFF_STATUS);
|
||||
}
|
||||
|
||||
int GetClientFd() const
|
||||
{
|
||||
return mClientFd;
|
||||
}
|
||||
|
||||
private:
|
||||
bt_status_t RecvMsg1()
|
||||
{
|
||||
struct iovec iv;
|
||||
memset(&iv, 0, sizeof(iv));
|
||||
iv.iov_base = mBuf;
|
||||
iv.iov_len = MSG1_SIZE;
|
||||
|
||||
struct msghdr msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = &iv;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
|
||||
if (res < 0) {
|
||||
return BT_STATUS_FAIL;
|
||||
}
|
||||
|
||||
mLen += res;
|
||||
|
||||
return BT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bt_status_t RecvMsg2()
|
||||
{
|
||||
struct iovec iv;
|
||||
memset(&iv, 0, sizeof(iv));
|
||||
iv.iov_base = mBuf + MSG1_SIZE;
|
||||
iv.iov_len = MSG2_SIZE;
|
||||
|
||||
struct msghdr msg;
|
||||
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = &iv;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
|
||||
if (res < 0) {
|
||||
return BT_STATUS_FAIL;
|
||||
}
|
||||
|
||||
mLen += res;
|
||||
|
||||
if (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) {
|
||||
return BT_STATUS_FAIL;
|
||||
}
|
||||
|
||||
struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
|
||||
|
||||
// Extract client fd from message header
|
||||
for (; cmsgptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
|
||||
if (CMSGHDR_CONTAINS_FD(cmsgptr)) {
|
||||
// if multiple file descriptors have been sent, we close
|
||||
// all but the final one.
|
||||
if (mClientFd != -1) {
|
||||
TEMP_FAILURE_RETRY(close(mClientFd));
|
||||
}
|
||||
// retrieve sent client fd
|
||||
mClientFd = *(static_cast<int*>(CMSG_DATA(cmsgptr)));
|
||||
}
|
||||
}
|
||||
|
||||
return BT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int16_t ReadInt16(unsigned long aOffset) const
|
||||
{
|
||||
/* little-endian buffer */
|
||||
return (static_cast<int16_t>(mBuf[aOffset + 1]) << 8) |
|
||||
static_cast<int16_t>(mBuf[aOffset]);
|
||||
}
|
||||
|
||||
int32_t ReadInt32(unsigned long aOffset) const
|
||||
{
|
||||
/* little-endian buffer */
|
||||
return (static_cast<int32_t>(mBuf[aOffset + 3]) << 24) |
|
||||
(static_cast<int32_t>(mBuf[aOffset + 2]) << 16) |
|
||||
(static_cast<int32_t>(mBuf[aOffset + 1]) << 8) |
|
||||
static_cast<int32_t>(mBuf[aOffset]);
|
||||
}
|
||||
|
||||
void ReadBdAddress(unsigned long aOffset, nsAString& aBdAddress) const
|
||||
{
|
||||
char str[18];
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mBuf[aOffset + 0], mBuf[aOffset + 1], mBuf[aOffset + 2],
|
||||
mBuf[aOffset + 3], mBuf[aOffset + 4], mBuf[aOffset + 5]);
|
||||
aBdAddress.AssignLiteral(str);
|
||||
}
|
||||
|
||||
MessageLoopForIO::FileDescriptorWatcher mWatcher;
|
||||
int mFd;
|
||||
int mClientFd;
|
||||
unsigned char mLen;
|
||||
uint8_t mBuf[MSG1_SIZE + MSG2_SIZE];
|
||||
};
|
||||
|
||||
/* |SocketMessageWatcherTask| starts a SocketMessageWatcher
|
||||
* on the I/O task
|
||||
*/
|
||||
class SocketMessageWatcherTask MOZ_FINAL : public Task
|
||||
{
|
||||
public:
|
||||
SocketMessageWatcherTask(SocketMessageWatcher* aWatcher)
|
||||
: mWatcher(aWatcher)
|
||||
{
|
||||
MOZ_ASSERT(mWatcher);
|
||||
}
|
||||
|
||||
void Run() MOZ_OVERRIDE
|
||||
{
|
||||
mWatcher->Watch();
|
||||
}
|
||||
|
||||
private:
|
||||
SocketMessageWatcher* mWatcher;
|
||||
};
|
||||
|
||||
/* |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;
|
||||
};
|
||||
|
||||
/* |ConnectWatcher| specializes SocketMessageWatcher for
|
||||
* connect operations by reading the socket messages from
|
||||
* Bluedroid and forwarding the connected socket to the
|
||||
* resource handler.
|
||||
*/
|
||||
class ConnectWatcher MOZ_FINAL : public SocketMessageWatcher
|
||||
{
|
||||
public:
|
||||
ConnectWatcher(int aFd, BluetoothSocketResultHandler* aRes)
|
||||
: SocketMessageWatcher(aFd)
|
||||
, mRes(aRes)
|
||||
{ }
|
||||
|
||||
void Proceed(bt_status_t aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
if (mRes) {
|
||||
DispatchBluetoothSocketResult(mRes,
|
||||
&BluetoothSocketResultHandler::Connect,
|
||||
GetFd(), GetBdAddress(),
|
||||
GetConnectionStatus(), aStatus);
|
||||
}
|
||||
MessageLoopForIO::current()->PostTask(
|
||||
FROM_HERE, new DeleteTask<ConnectWatcher>(this));
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothSocketResultHandler> mRes;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothSocketInterface::Connect(const bt_bdaddr_t* aBdAddr,
|
||||
btsock_type_t aType, const uint8_t* aUuid,
|
||||
int aChannel, int& aSockFd, int aFlags)
|
||||
int aChannel, int aFlags,
|
||||
BluetoothSocketResultHandler* aRes)
|
||||
{
|
||||
return mInterface->connect(aBdAddr, aType, aUuid, aChannel, &aSockFd,
|
||||
aFlags);
|
||||
int fd;
|
||||
|
||||
bt_status_t status = mInterface->connect(aBdAddr, aType, aUuid, aChannel,
|
||||
&fd, aFlags);
|
||||
if (status == BT_STATUS_SUCCESS) {
|
||||
/* receive Bluedroid's socket-setup messages */
|
||||
Task* t = new SocketMessageWatcherTask(new ConnectWatcher(fd, aRes));
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
|
||||
} else if (aRes) {
|
||||
DispatchBluetoothSocketResult(aRes,
|
||||
&BluetoothSocketResultHandler::Connect,
|
||||
-1, EmptyString(), 0, status);
|
||||
}
|
||||
}
|
||||
|
||||
/* |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 AcceptWatcher MOZ_FINAL : public SocketMessageWatcher
|
||||
{
|
||||
public:
|
||||
AcceptWatcher(int aFd, BluetoothSocketResultHandler* aRes)
|
||||
: SocketMessageWatcher(aFd)
|
||||
, mRes(aRes)
|
||||
{
|
||||
/* not supplying a result handler leaks received file descriptor */
|
||||
MOZ_ASSERT(mRes);
|
||||
}
|
||||
|
||||
void Proceed(bt_status_t aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
if (mRes) {
|
||||
DispatchBluetoothSocketResult(mRes,
|
||||
&BluetoothSocketResultHandler::Accept,
|
||||
GetClientFd(), GetBdAddress(),
|
||||
GetConnectionStatus(),
|
||||
aStatus);
|
||||
}
|
||||
MessageLoopForIO::current()->PostTask(
|
||||
FROM_HERE, new DeleteTask<AcceptWatcher>(this));
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothSocketResultHandler> mRes;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothSocketInterface::Accept(int aFd, BluetoothSocketResultHandler* aRes)
|
||||
{
|
||||
/* receive Bluedroid's socket-setup messages and client fd */
|
||||
Task* t = new SocketMessageWatcherTask(new AcceptWatcher(aFd, aRes));
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
|
||||
}
|
||||
|
||||
BluetoothSocketInterface::BluetoothSocketInterface(
|
||||
|
@ -24,6 +24,25 @@ class BluetoothInterface;
|
||||
// Socket Interface
|
||||
//
|
||||
|
||||
class BluetoothSocketResultHandler
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothSocketResultHandler)
|
||||
|
||||
virtual ~BluetoothSocketResultHandler() { }
|
||||
|
||||
virtual void OnError(bt_status_t aStatus)
|
||||
{
|
||||
BT_WARNING("Received error code %d", (int)aStatus);
|
||||
}
|
||||
|
||||
virtual void Listen(int aSockFd) { }
|
||||
virtual void Connect(int aSockFd, const nsAString& aBdAddress,
|
||||
int aConnectionState) { }
|
||||
virtual void Accept(int aSockFd, const nsAString& aBdAddress,
|
||||
int aConnectionState) { }
|
||||
};
|
||||
|
||||
class BluetoothSocketInterface
|
||||
{
|
||||
public:
|
||||
@ -31,13 +50,15 @@ public:
|
||||
|
||||
// Init and Cleanup is handled by BluetoothInterface
|
||||
|
||||
bt_status_t Listen(btsock_type_t aType,
|
||||
const char* aServiceName, const uint8_t* aServiceUuid,
|
||||
int aChannel, int& aSockFd, int aFlags);
|
||||
void Listen(btsock_type_t aType,
|
||||
const char* aServiceName, const uint8_t* aServiceUuid,
|
||||
int aChannel, int aFlags, BluetoothSocketResultHandler* aRes);
|
||||
|
||||
bt_status_t Connect(const bt_bdaddr_t* aBdAddr, btsock_type_t aType,
|
||||
const uint8_t* aUuid, int aChannel, int& aSockFd,
|
||||
int aFlags);
|
||||
void Connect(const bt_bdaddr_t* aBdAddr, btsock_type_t aType,
|
||||
const uint8_t* aUuid, int aChannel, int aFlags,
|
||||
BluetoothSocketResultHandler* aRes);
|
||||
|
||||
void Accept(int aFd, BluetoothSocketResultHandler* aRes);
|
||||
|
||||
protected:
|
||||
BluetoothSocketInterface(const btsock_interface_t* aInterface);
|
||||
|
@ -18,9 +18,6 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#define FIRST_SOCKET_INFO_MSG_LENGTH 4
|
||||
#define TOTAL_SOCKET_INFO_LENGTH 20
|
||||
|
||||
using namespace mozilla::ipc;
|
||||
USING_BLUETOOTH_NAMESPACE
|
||||
|
||||
@ -48,61 +45,55 @@ EnsureBluetoothSocketHalLoad()
|
||||
return true;
|
||||
}
|
||||
|
||||
static int16_t
|
||||
ReadInt16(const uint8_t* aData, size_t* aOffset)
|
||||
{
|
||||
int16_t value = (aData[*aOffset + 1] << 8) | aData[*aOffset];
|
||||
|
||||
*aOffset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
ReadInt32(const uint8_t* aData, size_t* aOffset)
|
||||
{
|
||||
int32_t value = (aData[*aOffset + 3] << 24) |
|
||||
(aData[*aOffset + 2] << 16) |
|
||||
(aData[*aOffset + 1] << 8) |
|
||||
aData[*aOffset];
|
||||
*aOffset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void
|
||||
ReadBdAddress(const uint8_t* aData, size_t* aOffset, nsAString& aDeviceAddress)
|
||||
{
|
||||
char bdstr[18];
|
||||
sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
aData[*aOffset], aData[*aOffset + 1], aData[*aOffset + 2],
|
||||
aData[*aOffset + 3], aData[*aOffset + 4], aData[*aOffset + 5]);
|
||||
|
||||
aDeviceAddress.AssignLiteral(bdstr);
|
||||
*aOffset += 6;
|
||||
}
|
||||
|
||||
class mozilla::dom::bluetooth::DroidSocketImpl : public ipc::UnixFdWatcher
|
||||
{
|
||||
public:
|
||||
/* The connection status in DroidSocketImpl indicates the current
|
||||
* phase of the socket connection. The initial settign should always
|
||||
* be DISCONNECTED, when no connection is present.
|
||||
*
|
||||
* To establish a connection on the server, DroidSocketImpl moves
|
||||
* to LISTENING. It now waits for incoming connection attempts by
|
||||
* installing a read watcher on the I/O thread. When its socket file
|
||||
* descriptor becomes readable, DroidSocketImpl accepts the connection
|
||||
* and finally moves DroidSocketImpl to CONNECTED. DroidSocketImpl now
|
||||
* uses read and write watchers during data transfers. Any socket setup
|
||||
* is handled internally by the accept method.
|
||||
*
|
||||
* On the client side, DroidSocketImpl moves to CONNECTING and installs
|
||||
* a write watcher on the I/O thread to wait until the connection is
|
||||
* ready. The socket setup is handled internally by the connect method.
|
||||
* Installing the write handler makes the code compatible with POSIX
|
||||
* semantics for non-blocking connects and gives a clear signal when the
|
||||
* conncetion is ready. DroidSocketImpl then moves to CONNECTED and uses
|
||||
* read and write watchers during data transfers.
|
||||
*/
|
||||
enum ConnectionStatus {
|
||||
SOCKET_IS_DISCONNECTED = 0,
|
||||
SOCKET_IS_LISTENING,
|
||||
SOCKET_IS_CONNECTING,
|
||||
SOCKET_IS_CONNECTED
|
||||
};
|
||||
|
||||
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer, int aFd)
|
||||
: ipc::UnixFdWatcher(aIOLoop, aFd)
|
||||
, mConsumer(aConsumer)
|
||||
, mReadMsgForClientFd(false)
|
||||
, mShuttingDownOnIOThread(false)
|
||||
, mChannel(0)
|
||||
, mAuth(false)
|
||||
, mEncrypt(false)
|
||||
{
|
||||
}
|
||||
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
|
||||
{ }
|
||||
|
||||
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer,
|
||||
int aChannel, bool aAuth, bool aEncrypt)
|
||||
: ipc::UnixFdWatcher(aIOLoop)
|
||||
, mConsumer(aConsumer)
|
||||
, mReadMsgForClientFd(false)
|
||||
, mShuttingDownOnIOThread(false)
|
||||
, mChannel(aChannel)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
|
||||
{ }
|
||||
|
||||
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer,
|
||||
@ -110,12 +101,12 @@ public:
|
||||
int aChannel, bool aAuth, bool aEncrypt)
|
||||
: ipc::UnixFdWatcher(aIOLoop)
|
||||
, mConsumer(aConsumer)
|
||||
, mReadMsgForClientFd(false)
|
||||
, mShuttingDownOnIOThread(false)
|
||||
, mDeviceAddress(aDeviceAddress)
|
||||
, mChannel(aChannel)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
|
||||
{
|
||||
MOZ_ASSERT(!mDeviceAddress.IsEmpty());
|
||||
}
|
||||
@ -159,24 +150,20 @@ public:
|
||||
mShuttingDownOnIOThread = true;
|
||||
}
|
||||
|
||||
void Connect();
|
||||
void Listen();
|
||||
|
||||
void SetUpIO(bool aWrite)
|
||||
{
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
if (aWrite) {
|
||||
AddWatchers(WRITE_WATCHER, false);
|
||||
}
|
||||
}
|
||||
void Connect(int aFd);
|
||||
void Listen(int aFd);
|
||||
void Accept(int aFd);
|
||||
|
||||
void ConnectClientFd()
|
||||
{
|
||||
// Stop current read watch
|
||||
RemoveWatchers(READ_WATCHER);
|
||||
|
||||
mConnectionStatus = SOCKET_IS_CONNECTED;
|
||||
|
||||
// Restart read & write watch on client fd
|
||||
SetUpIO(true);
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
AddWatchers(WRITE_WATCHER, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,11 +173,6 @@ public:
|
||||
*/
|
||||
RefPtr<BluetoothSocket> mConsumer;
|
||||
|
||||
/**
|
||||
* If true, read message header to get client fd.
|
||||
*/
|
||||
bool mReadMsgForClientFd;
|
||||
|
||||
private:
|
||||
/**
|
||||
* libevent triggered functions that reads data from socket when available and
|
||||
@ -208,14 +190,10 @@ private:
|
||||
*/
|
||||
virtual void OnFileCanWriteWithoutBlocking(int aFd);
|
||||
|
||||
/**
|
||||
* Read message to get data and client fd wrapped in message header
|
||||
*
|
||||
* @param aFd [in] File descriptor to read message from
|
||||
* @param aBuffer [out] Data buffer read
|
||||
* @param aLength [out] Number of bytes read
|
||||
*/
|
||||
ssize_t ReadMsg(int aFd, void *aBuffer, size_t aLength);
|
||||
void OnSocketCanReceiveWithoutBlocking(int aFd);
|
||||
void OnSocketCanAcceptWithoutBlocking(int aFd);
|
||||
void OnSocketCanSendWithoutBlocking(int aFd);
|
||||
void OnSocketCanConnectWithoutBlocking(int aFd);
|
||||
|
||||
/**
|
||||
* Raw data queue. Must be pushed/popped from IO thread only.
|
||||
@ -232,6 +210,7 @@ private:
|
||||
int mChannel;
|
||||
bool mAuth;
|
||||
bool mEncrypt;
|
||||
ConnectionStatus mConnectionStatus;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
@ -253,6 +232,70 @@ private:
|
||||
T* mInstance;
|
||||
};
|
||||
|
||||
class DroidSocketImplRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DroidSocketImpl* GetImpl() const
|
||||
{
|
||||
return mImpl;
|
||||
}
|
||||
|
||||
protected:
|
||||
DroidSocketImplRunnable(DroidSocketImpl* aImpl)
|
||||
: mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(aImpl);
|
||||
}
|
||||
|
||||
virtual ~DroidSocketImplRunnable()
|
||||
{ }
|
||||
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
class OnSocketEventRunnable : public DroidSocketImplRunnable
|
||||
{
|
||||
public:
|
||||
enum SocketEvent {
|
||||
CONNECT_SUCCESS,
|
||||
CONNECT_ERROR,
|
||||
DISCONNECT
|
||||
};
|
||||
|
||||
OnSocketEventRunnable(DroidSocketImpl* aImpl, SocketEvent e)
|
||||
: DroidSocketImplRunnable(aImpl)
|
||||
, mEvent(e)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
DroidSocketImpl* impl = GetImpl();
|
||||
|
||||
if (impl->IsShutdownOnMainThread()) {
|
||||
NS_WARNING("CloseSocket has already been called!");
|
||||
// Since we've already explicitly closed and the close happened before
|
||||
// this, this isn't really an error. Since we've warned, return OK.
|
||||
return NS_OK;
|
||||
}
|
||||
if (mEvent == CONNECT_SUCCESS) {
|
||||
impl->mConsumer->NotifySuccess();
|
||||
} else if (mEvent == CONNECT_ERROR) {
|
||||
impl->mConsumer->NotifyError();
|
||||
} else if (mEvent == DISCONNECT) {
|
||||
impl->mConsumer->NotifyDisconnect();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
SocketEvent mEvent;
|
||||
};
|
||||
|
||||
class RequestClosingSocketTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
@ -392,32 +435,40 @@ private:
|
||||
class SocketConnectTask : public DroidSocketImplTask
|
||||
{
|
||||
public:
|
||||
SocketConnectTask(DroidSocketImpl* aDroidSocketImpl)
|
||||
SocketConnectTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
|
||||
: DroidSocketImplTask(aDroidSocketImpl)
|
||||
, mFd(aFd)
|
||||
{ }
|
||||
|
||||
void Run() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsCanceled());
|
||||
GetDroidSocketImpl()->Connect();
|
||||
GetDroidSocketImpl()->Connect(mFd);
|
||||
}
|
||||
|
||||
private:
|
||||
int mFd;
|
||||
};
|
||||
|
||||
class SocketListenTask : public DroidSocketImplTask
|
||||
{
|
||||
public:
|
||||
SocketListenTask(DroidSocketImpl* aDroidSocketImpl)
|
||||
SocketListenTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
|
||||
: DroidSocketImplTask(aDroidSocketImpl)
|
||||
, mFd(aFd)
|
||||
{ }
|
||||
|
||||
void Run() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
if (!IsCanceled()) {
|
||||
GetDroidSocketImpl()->Listen();
|
||||
GetDroidSocketImpl()->Listen(mFd);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int mFd;
|
||||
};
|
||||
|
||||
class SocketConnectClientFdTask : public Task
|
||||
@ -434,127 +485,83 @@ public:
|
||||
};
|
||||
|
||||
void
|
||||
DroidSocketImpl::Connect()
|
||||
DroidSocketImpl::Connect(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(sBluetoothSocketInterface);
|
||||
MOZ_ASSERT(aFd >= 0);
|
||||
|
||||
bt_bdaddr_t remoteBdAddress;
|
||||
StringToBdAddressType(mDeviceAddress, &remoteBdAddress);
|
||||
|
||||
// TODO: uuid as argument
|
||||
int fd = -1;
|
||||
bt_status_t status =
|
||||
sBluetoothSocketInterface->Connect(&remoteBdAddress,
|
||||
BTSOCK_RFCOMM,
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
mChannel,
|
||||
fd,
|
||||
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
|
||||
(BTSOCK_FLAG_AUTH * mAuth));
|
||||
NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
|
||||
NS_ENSURE_TRUE_VOID(fd >= 0);
|
||||
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
|
||||
NS_ENSURE_TRUE_VOID(flags >= 0);
|
||||
|
||||
if (!(flags & O_NONBLOCK)) {
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
|
||||
NS_ENSURE_TRUE_VOID(!res);
|
||||
}
|
||||
|
||||
SetFd(fd);
|
||||
SetFd(aFd);
|
||||
mConnectionStatus = SOCKET_IS_CONNECTING;
|
||||
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
AddWatchers(WRITE_WATCHER, false);
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::Listen()
|
||||
DroidSocketImpl::Listen(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(sBluetoothSocketInterface);
|
||||
MOZ_ASSERT(aFd >= 0);
|
||||
|
||||
// TODO: uuid and service name as arguments
|
||||
|
||||
int fd = -1;
|
||||
bt_status_t status =
|
||||
sBluetoothSocketInterface->Listen(BTSOCK_RFCOMM,
|
||||
"OBEX Object Push",
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
mChannel,
|
||||
fd,
|
||||
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
|
||||
(BTSOCK_FLAG_AUTH * mAuth));
|
||||
NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
|
||||
NS_ENSURE_TRUE_VOID(fd >= 0);
|
||||
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
|
||||
NS_ENSURE_TRUE_VOID(flags >= 0);
|
||||
|
||||
if (!(flags & O_NONBLOCK)) {
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
|
||||
NS_ENSURE_TRUE_VOID(!res);
|
||||
}
|
||||
|
||||
SetFd(fd);
|
||||
SetFd(aFd);
|
||||
mConnectionStatus = SOCKET_IS_LISTENING;
|
||||
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
DroidSocketImpl::ReadMsg(int aFd, void *aBuffer, size_t aLength)
|
||||
void
|
||||
DroidSocketImpl::Accept(int aFd)
|
||||
{
|
||||
ssize_t ret;
|
||||
struct msghdr msg;
|
||||
struct iovec iv;
|
||||
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
|
||||
Close();
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&iv, 0, sizeof(iv));
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
|
||||
NS_ENSURE_TRUE_VOID(flags >= 0);
|
||||
|
||||
iv.iov_base = (unsigned char *)aBuffer;
|
||||
iv.iov_len = aLength;
|
||||
|
||||
msg.msg_iov = &iv;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
ret = recvmsg(GetFd(), &msg, MSG_NOSIGNAL);
|
||||
if (ret < 0 && errno == EPIPE) {
|
||||
// Treat this as an end of stream
|
||||
return 0;
|
||||
if (!(flags & O_NONBLOCK)) {
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
|
||||
NS_ENSURE_TRUE_VOID(!res);
|
||||
}
|
||||
|
||||
NS_ENSURE_FALSE(ret < 0, -1);
|
||||
NS_ENSURE_FALSE(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE), -1);
|
||||
SetFd(aFd);
|
||||
mConnectionStatus = SOCKET_IS_CONNECTED;
|
||||
|
||||
// Extract client fd from message header
|
||||
for (struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
|
||||
cmsgptr != nullptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
|
||||
if (cmsgptr->cmsg_level != SOL_SOCKET) {
|
||||
continue;
|
||||
}
|
||||
if (cmsgptr->cmsg_type == SCM_RIGHTS) {
|
||||
int *pDescriptors = (int *)CMSG_DATA(cmsgptr);
|
||||
nsRefPtr<OnSocketEventRunnable> r =
|
||||
new OnSocketEventRunnable(this, OnSocketEventRunnable::CONNECT_SUCCESS);
|
||||
NS_DispatchToMainThread(r);
|
||||
|
||||
// Overwrite fd with client fd
|
||||
int fd = pDescriptors[0];
|
||||
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
|
||||
NS_ENSURE_TRUE(flags >= 0, 0);
|
||||
if (!(flags & O_NONBLOCK)) {
|
||||
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
|
||||
NS_ENSURE_TRUE(!res, 0);
|
||||
}
|
||||
Close();
|
||||
SetFd(fd);
|
||||
break;
|
||||
}
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
if (!mOutgoingQ.IsEmpty()) {
|
||||
AddWatchers(WRITE_WATCHER, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
|
||||
{
|
||||
if (mConnectionStatus == SOCKET_IS_CONNECTED) {
|
||||
OnSocketCanReceiveWithoutBlocking(aFd);
|
||||
} else if (mConnectionStatus == SOCKET_IS_LISTENING) {
|
||||
OnSocketCanAcceptWithoutBlocking(aFd);
|
||||
} else {
|
||||
NS_NOTREACHED("invalid connection state for reading");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnSocketCanReceiveWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
@ -563,12 +570,7 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
|
||||
while (true) {
|
||||
nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
|
||||
|
||||
ssize_t ret;
|
||||
if (!mReadMsgForClientFd) {
|
||||
ret = read(aFd, incoming->mData, incoming->mSize);
|
||||
} else {
|
||||
ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
|
||||
}
|
||||
ssize_t ret = read(aFd, incoming->mData, incoming->mSize);
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret == -1) {
|
||||
@ -606,8 +608,113 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
|
||||
MOZ_CRASH("We returned early");
|
||||
}
|
||||
|
||||
class AcceptTask MOZ_FINAL : public DroidSocketImplTask
|
||||
{
|
||||
public:
|
||||
AcceptTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
|
||||
: DroidSocketImplTask(aDroidSocketImpl)
|
||||
, mFd(aFd)
|
||||
{ }
|
||||
|
||||
void Run() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsCanceled());
|
||||
|
||||
GetDroidSocketImpl()->Accept(mFd);
|
||||
}
|
||||
|
||||
private:
|
||||
int mFd;
|
||||
};
|
||||
|
||||
class AcceptResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
|
||||
{
|
||||
public:
|
||||
AcceptResultHandler(DroidSocketImpl* aImpl)
|
||||
: mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(mImpl);
|
||||
}
|
||||
|
||||
void Accept(int aFd, const nsAString& aBdAddress,
|
||||
int aConnectionStatus) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mImpl->IsShutdownOnMainThread()) {
|
||||
BT_LOGD("mConsumer is null, aborting receive!");
|
||||
return;
|
||||
}
|
||||
|
||||
mImpl->mConsumer->SetAddress(aBdAddress);
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new AcceptTask(mImpl, aFd));
|
||||
}
|
||||
|
||||
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
BT_LOGR("BluetoothSocketInterface::Accept failed: %d", (int)aStatus);
|
||||
}
|
||||
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
class AcceptRunnable MOZ_FINAL : public nsRunnable
|
||||
{
|
||||
public:
|
||||
AcceptRunnable(int aFd, DroidSocketImpl* aImpl)
|
||||
: mFd(aFd)
|
||||
, mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(mImpl);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(sBluetoothSocketInterface);
|
||||
|
||||
sBluetoothSocketInterface->Accept(mFd, new AcceptResultHandler(mImpl));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
int mFd;
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnSocketCanAcceptWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
|
||||
/* When a listening socket is ready for receiving data,
|
||||
* we can call |Accept| on it.
|
||||
*/
|
||||
|
||||
RemoveWatchers(READ_WATCHER);
|
||||
nsRefPtr<AcceptRunnable> t = new AcceptRunnable(aFd, this);
|
||||
NS_DispatchToMainThread(t);
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
|
||||
{
|
||||
if (mConnectionStatus == SOCKET_IS_CONNECTED) {
|
||||
OnSocketCanSendWithoutBlocking(aFd);
|
||||
} else if (mConnectionStatus == SOCKET_IS_CONNECTING) {
|
||||
OnSocketCanConnectWithoutBlocking(aFd);
|
||||
} else {
|
||||
NS_NOTREACHED("invalid connection state for writing");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnSocketCanSendWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
@ -649,6 +756,28 @@ DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnSocketCanConnectWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
|
||||
/* We follow Posix behaviour here: Connect operations are
|
||||
* complete once we can write to the connecting socket.
|
||||
*/
|
||||
|
||||
mConnectionStatus = SOCKET_IS_CONNECTED;
|
||||
|
||||
nsRefPtr<OnSocketEventRunnable> r =
|
||||
new OnSocketEventRunnable(this, OnSocketEventRunnable::CONNECT_SUCCESS);
|
||||
NS_DispatchToMainThread(r);
|
||||
|
||||
AddWatchers(READ_WATCHER, true);
|
||||
if (!mOutgoingQ.IsEmpty()) {
|
||||
AddWatchers(WRITE_WATCHER, false);
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
|
||||
BluetoothSocketType aType,
|
||||
bool aAuth,
|
||||
@ -657,7 +786,6 @@ BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
|
||||
, mImpl(nullptr)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
, mReceivedSocketInfoLength(0)
|
||||
{
|
||||
MOZ_ASSERT(aObserver);
|
||||
|
||||
@ -681,35 +809,107 @@ BluetoothSocket::CloseDroidSocket()
|
||||
new ShutdownSocketTask(mImpl));
|
||||
mImpl = nullptr;
|
||||
|
||||
OnDisconnect();
|
||||
NotifyDisconnect();
|
||||
}
|
||||
|
||||
class ConnectResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
|
||||
{
|
||||
public:
|
||||
ConnectResultHandler(DroidSocketImpl* aImpl)
|
||||
: mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(mImpl);
|
||||
}
|
||||
|
||||
void Connect(int aFd, const nsAString& aBdAddress,
|
||||
int aConnectionStatus) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mImpl->IsShutdownOnMainThread()) {
|
||||
mImpl->mConsumer->SetAddress(aBdAddress);
|
||||
}
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketConnectTask(mImpl, aFd));
|
||||
}
|
||||
|
||||
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
BT_WARNING("Connect failed: %d", (int)aStatus);
|
||||
}
|
||||
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
bool
|
||||
BluetoothSocket::Connect(const nsAString& aDeviceAddress, int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ENSURE_FALSE(mImpl, false);
|
||||
|
||||
mIsServer = false;
|
||||
mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this, aDeviceAddress,
|
||||
aChannel, mAuth, mEncrypt);
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketConnectTask(mImpl));
|
||||
|
||||
bt_bdaddr_t remoteBdAddress;
|
||||
StringToBdAddressType(aDeviceAddress, &remoteBdAddress);
|
||||
|
||||
// TODO: uuid as argument
|
||||
sBluetoothSocketInterface->Connect(&remoteBdAddress,
|
||||
BTSOCK_RFCOMM,
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
aChannel,
|
||||
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
|
||||
(BTSOCK_FLAG_AUTH * mAuth),
|
||||
new ConnectResultHandler(mImpl));
|
||||
return true;
|
||||
}
|
||||
|
||||
class ListenResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
|
||||
{
|
||||
public:
|
||||
ListenResultHandler(DroidSocketImpl* aImpl)
|
||||
: mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(mImpl);
|
||||
}
|
||||
|
||||
void Listen(int aFd) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketListenTask(mImpl, aFd));
|
||||
}
|
||||
|
||||
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
BT_WARNING("Listen failed: %d", (int)aStatus);
|
||||
}
|
||||
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
bool
|
||||
BluetoothSocket::Listen(int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ENSURE_FALSE(mImpl, false);
|
||||
|
||||
mIsServer = true;
|
||||
mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this, aChannel, mAuth,
|
||||
mEncrypt);
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketListenTask(mImpl));
|
||||
|
||||
sBluetoothSocketInterface->Listen(BTSOCK_RFCOMM,
|
||||
"OBEX Object Push",
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
aChannel,
|
||||
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
|
||||
(BTSOCK_FLAG_AUTH * mAuth),
|
||||
new ListenResultHandler(mImpl));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -725,64 +925,9 @@ BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
/**
|
||||
* 2 socket info messages (20 bytes) to receive at the beginning:
|
||||
* - 1st message: [channel:4]
|
||||
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
|
||||
*/
|
||||
if (mReceivedSocketInfoLength >= TOTAL_SOCKET_INFO_LENGTH) {
|
||||
// We've got both socket info messages
|
||||
return false;
|
||||
}
|
||||
mReceivedSocketInfoLength += aMessage->mSize;
|
||||
|
||||
size_t offset = 0;
|
||||
if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) {
|
||||
// 1st message: [channel:4]
|
||||
int32_t channel = ReadInt32(aMessage->mData, &offset);
|
||||
BT_LOGR("channel %d", channel);
|
||||
|
||||
// If this is server socket, read header of next message for client fd
|
||||
mImpl->mReadMsgForClientFd = mIsServer;
|
||||
} else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) {
|
||||
// 2nd message: [size:2][bd address:6][channel:4][connection status:4]
|
||||
int16_t size = ReadInt16(aMessage->mData, &offset);
|
||||
ReadBdAddress(aMessage->mData, &offset, mDeviceAddress);
|
||||
int32_t channel = ReadInt32(aMessage->mData, &offset);
|
||||
int32_t connectionStatus = ReadInt32(aMessage->mData, &offset);
|
||||
|
||||
BT_LOGR("size %d channel %d remote addr %s status %d",
|
||||
size, channel, NS_ConvertUTF16toUTF8(mDeviceAddress).get(), connectionStatus);
|
||||
|
||||
if (connectionStatus != 0) {
|
||||
OnConnectError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mIsServer) {
|
||||
mImpl->mReadMsgForClientFd = false;
|
||||
// Connect client fd on IO thread
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketConnectClientFdTask(mImpl));
|
||||
}
|
||||
OnConnectSuccess();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
|
||||
{
|
||||
if (ReceiveSocketInfo(aMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->ReceiveSocketData(this, aMessage);
|
||||
@ -811,4 +956,3 @@ BluetoothSocket::OnDisconnect()
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketDisconnect(this);
|
||||
}
|
||||
|
||||
|
@ -23,30 +23,8 @@ public:
|
||||
bool aAuth,
|
||||
bool aEncrypt);
|
||||
|
||||
/**
|
||||
* Connect to remote server as a client.
|
||||
*
|
||||
* The steps are as following:
|
||||
* 1) BluetoothSocket acquires fd from bluedroid, and creates
|
||||
* a DroidSocketImpl to watch read/write of the fd.
|
||||
* 2) DroidSocketImpl receives first 2 messages to get socket info.
|
||||
* 3) Obex client session starts.
|
||||
*/
|
||||
bool Connect(const nsAString& aDeviceAddress, int aChannel);
|
||||
|
||||
/**
|
||||
* Listen to incoming connection as a server.
|
||||
*
|
||||
* The steps are as following:
|
||||
* 1) BluetoothSocket acquires fd from bluedroid, and creates
|
||||
* a DroidSocketImpl to watch read of the fd. DroidSocketImpl
|
||||
* receives the 1st message immediately.
|
||||
* 2) When there's incoming connection, DroidSocketImpl receives
|
||||
* 2nd message to get socket info and client fd.
|
||||
* 3) DroidSocketImpl stops watching read of original fd and
|
||||
* starts to watch read/write of client fd.
|
||||
* 4) Obex server session starts.
|
||||
*/
|
||||
bool Listen(int aChannel);
|
||||
|
||||
inline void Disconnect()
|
||||
@ -65,6 +43,11 @@ public:
|
||||
aDeviceAddress = mDeviceAddress;
|
||||
}
|
||||
|
||||
inline void SetAddress(const nsAString& aDeviceAddress)
|
||||
{
|
||||
mDeviceAddress = aDeviceAddress;
|
||||
}
|
||||
|
||||
void CloseDroidSocket();
|
||||
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
|
||||
|
||||
@ -74,10 +57,6 @@ private:
|
||||
nsString mDeviceAddress;
|
||||
bool mAuth;
|
||||
bool mEncrypt;
|
||||
bool mIsServer;
|
||||
int mReceivedSocketInfoLength;
|
||||
|
||||
bool ReceiveSocketInfo(nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage);
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
@ -561,6 +561,43 @@ function waitForAdapterAttributeChanged(aAdapter, aAttrName, aExpectedValue) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for specified number of 'devicefound' events.
|
||||
*
|
||||
* Resolve if specified number of devices has been found. Never reject.
|
||||
*
|
||||
* Fulfill params: an array which contains BluetoothDeviceEvents that we
|
||||
* received from the BluetoothDiscoveryHandle.
|
||||
*
|
||||
* @param aDiscoveryHandle
|
||||
* A BluetoothDiscoveryHandle which is used to notify application of
|
||||
* discovered remote bluetooth devices.
|
||||
* @param aExpectedNumberOfDevices
|
||||
* The number of remote devices we expect to discovery.
|
||||
*
|
||||
* @return A deferred promise.
|
||||
*/
|
||||
function waitForDevicesFound(aDiscoveryHandle, aExpectedNumberOfDevices) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
ok(aDiscoveryHandle instanceof BluetoothDiscoveryHandle,
|
||||
"discoveryHandle should be a BluetoothDiscoveryHandle");
|
||||
|
||||
let devicesArray = [];
|
||||
aDiscoveryHandle.ondevicefound = function onDeviceFound(aEvent) {
|
||||
ok(aEvent instanceof BluetoothDeviceEvent,
|
||||
"aEvent should be a BluetoothDeviceEvent");
|
||||
|
||||
devicesArray.push(aEvent);
|
||||
if (devicesArray.length >= aExpectedNumberOfDevices) {
|
||||
aDiscoveryHandle.ondevicefound = null;
|
||||
deferred.resolve(devicesArray);
|
||||
}
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush permission settings and call |finish()|.
|
||||
*/
|
||||
|
@ -6,3 +6,4 @@ qemu = false
|
||||
[test_dom_BluetoothManager_API2.js]
|
||||
[test_dom_BluetoothAdapter_enable_API2.js]
|
||||
[test_dom_BluetoothAdapter_setters_API2.js]
|
||||
[test_dom_BluetoothAdapter_discovery_API2.js]
|
||||
|
@ -0,0 +1,124 @@
|
||||
/* 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/. */
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test Purpose:
|
||||
// To verify the discovery process of BluetoothAdapter.
|
||||
// Testers have to put the B2G devices in an environment which is surrounded
|
||||
// by N discoverable remote devices. To pass this test, the number N has to be
|
||||
// greater or equals than EXPECTED_NUMBER_OF_REMOTE_DEVICES.
|
||||
//
|
||||
// Test Procedure:
|
||||
// [0] Set Bluetooth permission and enable default adapter.
|
||||
// [1] Start discovery and verify the correctness.
|
||||
// [2] Attach event handler for 'ondevicefound'.
|
||||
// [3] Stop discovery and verify the correctness.
|
||||
// [4] Mark the BluetoothDiscoveryHandle from [1] as expired.
|
||||
// [5] Start discovery and verify the correctness.
|
||||
// [6] Wait for 'devicefound' events.
|
||||
// [7] Stop discovery and verify the correctness.
|
||||
// [8] Call 'startDiscovery' twice continuously.
|
||||
// [9] Call 'stopDiscovery' twice continuously.
|
||||
// [10] Clean up the event handler of [2].
|
||||
//
|
||||
// Test Coverage:
|
||||
// - BluetoothAdapter.discovering
|
||||
// - BluetoothAdapter.startDiscovery()
|
||||
// - BluetoothAdapter.stopDiscovery()
|
||||
// - BluetoothAdapter.onattributechanged()
|
||||
// - BluetoothDiscoveryHandle.ondevicefound()
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = 'head.js';
|
||||
|
||||
const EXPECTED_NUMBER_OF_REMOTE_DEVICES = 2;
|
||||
|
||||
startBluetoothTest(true, function testCaseMain(aAdapter) {
|
||||
log("Checking adapter attributes ...");
|
||||
|
||||
is(aAdapter.state, "enabled", "adapter.state");
|
||||
isnot(aAdapter.address, "", "adapter.address");
|
||||
|
||||
// Since adapter has just been re-enabled, these properties should be 'false'.
|
||||
is(aAdapter.discovering, false, "adapter.discovering");
|
||||
is(aAdapter.discoverable, false, "adapter.discoverable");
|
||||
|
||||
log("adapter.address: " + aAdapter.address);
|
||||
log("adapter.name: " + aAdapter.name);
|
||||
|
||||
let discoveryHandle = null;
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
log("[1] Start discovery and verify the correctness ... ");
|
||||
let promises = [];
|
||||
promises.push(waitForAdapterAttributeChanged(aAdapter, "discovering", true));
|
||||
promises.push(aAdapter.startDiscovery());
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(function(aResults) {
|
||||
log("[2] Attach event handler for 'ondevicefound' ... ");
|
||||
discoveryHandle = aResults[1];
|
||||
isHandleExpired = false;
|
||||
discoveryHandle.ondevicefound = function onDeviceFound(aEvent) {
|
||||
if (isHandleExpired) {
|
||||
ok(false, "Expired BluetoothDiscoveryHandle received an event.");
|
||||
}
|
||||
};
|
||||
})
|
||||
.then(function() {
|
||||
log("[3] Stop discovery and and verify the correctness ... ");
|
||||
let promises = [];
|
||||
if (aAdapter.discovering) {
|
||||
promises.push(waitForAdapterAttributeChanged(aAdapter, "discovering", false));
|
||||
}
|
||||
promises.push(aAdapter.stopDiscovery());
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(function() {
|
||||
log("[4] Mark the BluetoothDiscoveryHandle from [1] as expired ... ");
|
||||
isHandleExpired = true;
|
||||
})
|
||||
.then(function() {
|
||||
log("[5] Start discovery and verify the correctness ... ");
|
||||
let promises = [];
|
||||
promises.push(waitForAdapterAttributeChanged(aAdapter, "discovering", true));
|
||||
promises.push(aAdapter.startDiscovery());
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(function(aResults) {
|
||||
log("[6] Wait for 'devicefound' events ... ");
|
||||
return waitForDevicesFound(aResults[1], EXPECTED_NUMBER_OF_REMOTE_DEVICES);
|
||||
})
|
||||
.then(function() {
|
||||
log("[7] Stop discovery and and verify the correctness ... ");
|
||||
let promises = [];
|
||||
if (aAdapter.discovering) {
|
||||
promises.push(waitForAdapterAttributeChanged(aAdapter, "discovering", false));
|
||||
}
|
||||
promises.push(aAdapter.stopDiscovery());
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(function() {
|
||||
log("[8] Call 'startDiscovery' twice continuously ... ");
|
||||
return aAdapter.startDiscovery()
|
||||
.then(() => aAdapter.startDiscovery())
|
||||
.then(() => ok(false, "Call startDiscovery() when adapter is discovering. - Fail"),
|
||||
() => ok(true, "Call startDiscovery() when adapter is discovering. - Success"));
|
||||
})
|
||||
.then(function() {
|
||||
log("[9] Call 'stopDiscovery' twice continuously ... ");
|
||||
return aAdapter.stopDiscovery()
|
||||
.then(() => aAdapter.stopDiscovery())
|
||||
.then(() => ok(true, "Call stopDiscovery() when adapter isn't discovering. - Success"),
|
||||
() => ok(false, "Call stopDiscovery() when adapter isn't discovering. - Fail"));
|
||||
})
|
||||
.then(function() {
|
||||
log("[10] Clean up the event handler of [2] ... ");
|
||||
if (discoveryHandle) {
|
||||
discoveryHandle.ondevicefound = null;
|
||||
}
|
||||
});
|
||||
});
|
@ -401,49 +401,17 @@ child:
|
||||
|
||||
UpdateFrame(FrameMetrics frame);
|
||||
|
||||
/**
|
||||
* Acknowledge the receipt of a scroll offset update from the content
|
||||
* process. This is used to manage concurrent scroll updates from many
|
||||
* sources.
|
||||
*/
|
||||
// The following methods correspond to functions on the GeckoContentController
|
||||
// interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
|
||||
// in that file for these functions.
|
||||
AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
|
||||
|
||||
/**
|
||||
* Requests handling of a double tap. |point| is in CSS pixels, relative to
|
||||
* the scroll offset. This message is expected to round-trip back to
|
||||
* ZoomToRect() with a rect indicating where we should zoom to.
|
||||
*/
|
||||
HandleDoubleTap(CSSPoint point, ScrollableLayerGuid aGuid);
|
||||
|
||||
/**
|
||||
* Requests handling of a single tap. |point| is in CSS pixels, relative to
|
||||
* the scroll offset. This message is expected to send a "mousedown" and
|
||||
* "mouseup" series of events at this point.
|
||||
*/
|
||||
HandleSingleTap(CSSPoint point, ScrollableLayerGuid aGuid);
|
||||
|
||||
/**
|
||||
* Requests handling of a long tap. |point| is in CSS pixels, relative to
|
||||
* the scroll offset. This message is expected to send a "contextmenu"
|
||||
* events at this point.
|
||||
*/
|
||||
HandleLongTap(CSSPoint point, ScrollableLayerGuid aGuid);
|
||||
|
||||
/**
|
||||
* Requests handling of releasing a long tap. |aPoint| is in CSS pixels,
|
||||
* relative to the current scroll offset. In the case the "contextmenu"
|
||||
* event generated by the preceding HandleLongTap call was not handled,
|
||||
* this message is expected to generate a "mousedown" and "mouseup"
|
||||
* series of events
|
||||
*/
|
||||
HandleLongTapUp(CSSPoint point, ScrollableLayerGuid aGuid);
|
||||
|
||||
/**
|
||||
* Notifies the child about various APZ state changes.
|
||||
* See GeckoContentController::NotifyAPZStateChange() for details.
|
||||
*/
|
||||
NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
|
||||
|
||||
|
||||
/**
|
||||
* Sending an activate message moves focus to the child.
|
||||
*/
|
||||
|
@ -702,9 +702,7 @@ TabChild::TabChild(nsIContentChild* aManager, const TabContext& aContext, uint32
|
||||
, mTriedBrowserInit(false)
|
||||
, mOrientation(eScreenOrientation_PortraitPrimary)
|
||||
, mUpdateHitRegion(false)
|
||||
, mContextMenuHandled(false)
|
||||
, mLongTapEventHandled(false)
|
||||
, mWaitingTouchListeners(false)
|
||||
, mPendingTouchPreventedResponse(false)
|
||||
, mIgnoreKeyPressEvent(false)
|
||||
, mActiveElementManager(new ActiveElementManager())
|
||||
, mHasValidInnerSize(false)
|
||||
@ -1805,23 +1803,23 @@ TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& a
|
||||
return true;
|
||||
}
|
||||
|
||||
mContextMenuHandled =
|
||||
bool eventHandled =
|
||||
DispatchMouseEvent(NS_LITERAL_STRING("contextmenu"),
|
||||
APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid),
|
||||
2, 1, 0, false,
|
||||
nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
|
||||
|
||||
// If no one handle context menu, fire MOZLONGTAP event
|
||||
if (!mContextMenuHandled) {
|
||||
if (!eventHandled) {
|
||||
LayoutDevicePoint currentPoint =
|
||||
APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid) * mWidget->GetDefaultScale();
|
||||
int time = 0;
|
||||
nsEventStatus status =
|
||||
DispatchSynthesizedMouseEvent(NS_MOUSE_MOZLONGTAP, time, currentPoint, mWidget);
|
||||
mLongTapEventHandled = (status == nsEventStatus_eConsumeNoDefault);
|
||||
eventHandled = (status == nsEventStatus_eConsumeNoDefault);
|
||||
}
|
||||
|
||||
SendContentReceivedTouch(aGuid, mContextMenuHandled || mLongTapEventHandled);
|
||||
SendContentReceivedTouch(aGuid, eventHandled);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1829,16 +1827,6 @@ TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& a
|
||||
bool
|
||||
TabChild::RecvHandleLongTapUp(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
|
||||
{
|
||||
if (mContextMenuHandled) {
|
||||
mContextMenuHandled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mLongTapEventHandled) {
|
||||
mLongTapEventHandled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
RecvHandleSingleTap(aPoint, aGuid);
|
||||
return true;
|
||||
}
|
||||
@ -2095,23 +2083,21 @@ TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
|
||||
mActiveElementManager->SetTargetElement(localEvent.touches[0]->GetTarget());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> outerWindow = do_GetInterface(WebNavigation());
|
||||
nsCOMPtr<nsPIDOMWindow> innerWindow = outerWindow ? outerWindow->GetCurrentInnerWindow() : nullptr;
|
||||
|
||||
if (!innerWindow || (!innerWindow->HasTouchEventListeners() &&
|
||||
!innerWindow->MayHaveTouchCaret())) {
|
||||
SendContentReceivedTouch(aGuid, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isTouchPrevented = nsIPresShell::gPreventMouseEvents ||
|
||||
localEvent.mFlags.mMultipleActionsPrevented;
|
||||
switch (aEvent.message) {
|
||||
case NS_TOUCH_START: {
|
||||
if (mPendingTouchPreventedResponse) {
|
||||
// We can enter here if we get two TOUCH_STARTs in a row and didn't
|
||||
// respond to the first one. Respond to it now.
|
||||
SendContentReceivedTouch(mPendingTouchPreventedGuid, false);
|
||||
mPendingTouchPreventedResponse = false;
|
||||
}
|
||||
if (isTouchPrevented) {
|
||||
SendContentReceivedTouch(aGuid, isTouchPrevented);
|
||||
} else {
|
||||
mWaitingTouchListeners = true;
|
||||
mPendingTouchPreventedResponse = true;
|
||||
mPendingTouchPreventedGuid = aGuid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -2119,9 +2105,10 @@ TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
|
||||
case NS_TOUCH_MOVE:
|
||||
case NS_TOUCH_END:
|
||||
case NS_TOUCH_CANCEL: {
|
||||
if (mWaitingTouchListeners) {
|
||||
SendContentReceivedTouch(aGuid, isTouchPrevented);
|
||||
mWaitingTouchListeners = false;
|
||||
if (mPendingTouchPreventedResponse) {
|
||||
MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
|
||||
SendContentReceivedTouch(mPendingTouchPreventedGuid, isTouchPrevented);
|
||||
mPendingTouchPreventedResponse = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -586,9 +586,8 @@ private:
|
||||
bool mTriedBrowserInit;
|
||||
ScreenOrientation mOrientation;
|
||||
bool mUpdateHitRegion;
|
||||
bool mContextMenuHandled;
|
||||
bool mLongTapEventHandled;
|
||||
bool mWaitingTouchListeners;
|
||||
bool mPendingTouchPreventedResponse;
|
||||
ScrollableLayerGuid mPendingTouchPreventedGuid;
|
||||
void FireSingleTapEvent(LayoutDevicePoint aPoint);
|
||||
|
||||
bool mIgnoreKeyPressEvent;
|
||||
|
@ -310,6 +310,9 @@ VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
|
||||
mHasFacingMode = true;
|
||||
mFacingMode = dom::VideoFacingModeEnum::User;
|
||||
}
|
||||
|
||||
// dom::MediaSourceEnum::Camera;
|
||||
mMediaSource = aSource->GetMediaSource();
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
|
||||
@ -361,6 +364,15 @@ MediaDevice::GetFacingMode(nsAString& aFacingMode)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaDevice::GetMediaSource(nsAString& aMediaSource)
|
||||
{
|
||||
|
||||
aMediaSource.Assign(NS_ConvertUTF8toUTF16(
|
||||
dom::MediaSourceEnumValues::strings[uint32_t(mMediaSource)].value));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MediaEngineVideoSource*
|
||||
VideoDevice::GetSource()
|
||||
{
|
||||
@ -714,14 +726,19 @@ static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
|
||||
const MediaTrackConstraintSet &aConstraints,
|
||||
nsIMediaDevice &aCandidate)
|
||||
{
|
||||
nsString s;
|
||||
if (aConstraints.mFacingMode.WasPassed()) {
|
||||
nsString s;
|
||||
aCandidate.GetFacingMode(s);
|
||||
if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[
|
||||
uint32_t(aConstraints.mFacingMode.Value())].value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
aCandidate.GetMediaSource(s);
|
||||
if (!s.EqualsASCII(dom::MediaSourceEnumValues::strings[
|
||||
uint32_t(aConstraints.mMediaSource)].value)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Add more video-specific constraints
|
||||
return true;
|
||||
}
|
||||
@ -742,7 +759,7 @@ template<class SourceType, class ConstraintsType>
|
||||
static SourceSet *
|
||||
GetSources(MediaEngine *engine,
|
||||
ConstraintsType &aConstraints,
|
||||
void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
|
||||
void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum, nsTArray<nsRefPtr<SourceType> >*),
|
||||
const char* media_device_name = nullptr)
|
||||
{
|
||||
ScopedDeletePtr<SourceSet> result(new SourceSet);
|
||||
@ -753,7 +770,7 @@ static SourceSet *
|
||||
SourceSet candidateSet;
|
||||
{
|
||||
nsTArray<nsRefPtr<SourceType> > sources;
|
||||
(engine->*aEnumerate)(&sources);
|
||||
(engine->*aEnumerate)(aConstraints.mMediaSource, &sources);
|
||||
/**
|
||||
* We're allowing multiple tabs to access the same camera for parity
|
||||
* with Chrome. See bug 811757 for some of the issues surrounding
|
||||
@ -1018,8 +1035,8 @@ public:
|
||||
MOZ_ASSERT(mError);
|
||||
if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
|
||||
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
|
||||
ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
|
||||
&MediaEngine::EnumerateVideoDevices));
|
||||
ScopedDeletePtr<SourceSet> sources(GetSources(backend, constraints,
|
||||
&MediaEngine::EnumerateVideoDevices));
|
||||
|
||||
if (!sources->Length()) {
|
||||
Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
|
||||
@ -1180,8 +1197,8 @@ public:
|
||||
if (IsOn(mConstraints.mVideo)) {
|
||||
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
|
||||
ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints,
|
||||
&MediaEngine::EnumerateVideoDevices,
|
||||
mLoopbackVideoDevice.get()));
|
||||
&MediaEngine::EnumerateVideoDevices,
|
||||
mLoopbackVideoDevice.get()));
|
||||
final->MoveElementsFrom(*s);
|
||||
}
|
||||
if (IsOn(mConstraints.mAudio)) {
|
||||
@ -1191,6 +1208,7 @@ public:
|
||||
mLoopbackAudioDevice.get()));
|
||||
final->MoveElementsFrom(*s);
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,
|
||||
mSuccess, mError,
|
||||
final.forget()));
|
||||
@ -1465,6 +1483,15 @@ MediaManager::GetUserMedia(bool aPrivileged,
|
||||
onError.forget(), windowID, listener, mPrefs);
|
||||
}
|
||||
|
||||
// deny screensharing request if support is disabled
|
||||
if (c.mVideo.IsMediaTrackConstraints() &&
|
||||
!Preferences::GetBool("media.getusermedia.screensharing.enabled", false)) {
|
||||
auto& tc = c.mVideo.GetAsMediaTrackConstraints();
|
||||
if (tc.mMediaSource != dom::MediaSourceEnum::Camera) {
|
||||
return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_B2G_CAMERA
|
||||
if (mCameraManager == nullptr) {
|
||||
mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
|
||||
@ -1479,20 +1506,17 @@ MediaManager::GetUserMedia(bool aPrivileged,
|
||||
}
|
||||
#endif
|
||||
nsIURI* docURI = aWindow->GetDocumentURI();
|
||||
#ifdef MOZ_LOOP
|
||||
{
|
||||
bool isLoop = false;
|
||||
nsCOMPtr<nsIURI> loopURI;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = docURI->EqualsExceptRef(loopURI, &isLoop);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (isLoop) {
|
||||
aPrivileged = true;
|
||||
}
|
||||
bool isLoop = false;
|
||||
nsCOMPtr<nsIURI> loopURI;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = docURI->EqualsExceptRef(loopURI, &isLoop);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (isLoop) {
|
||||
aPrivileged = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// XXX No full support for picture in Desktop yet (needs proper UI)
|
||||
if (aPrivileged ||
|
||||
|
@ -486,6 +486,7 @@ protected:
|
||||
nsString mID;
|
||||
bool mHasFacingMode;
|
||||
dom::VideoFacingModeEnum mFacingMode;
|
||||
dom::MediaSourceEnum mMediaSource;
|
||||
nsRefPtr<MediaEngineSource> mSource;
|
||||
};
|
||||
|
||||
|
@ -358,14 +358,16 @@ RTCPeerConnection.prototype = {
|
||||
|
||||
callCB: function(callback, arg) {
|
||||
if (callback) {
|
||||
try {
|
||||
callback(arg);
|
||||
} catch(e) {
|
||||
// A content script (user-provided) callback threw an error. We don't
|
||||
// want this to take down peerconnection, but we still want the user
|
||||
// to see it, so we catch it, report it, and move on.
|
||||
this.logErrorAndCallOnError(e.message, e.fileName, e.lineNumber);
|
||||
}
|
||||
this._win.setTimeout(() => {
|
||||
try {
|
||||
callback(arg);
|
||||
} catch(e) {
|
||||
// A content script (user-provided) callback threw an error. We don't
|
||||
// want this to take down peerconnection, but we still want the user
|
||||
// to see it, so we catch it, report it, and move on.
|
||||
this.logErrorAndCallOnError(e.message, e.fileName, e.lineNumber);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -13,6 +13,7 @@ interface nsIMediaDevice : nsISupports
|
||||
readonly attribute DOMString name;
|
||||
readonly attribute DOMString id;
|
||||
readonly attribute DOMString facingMode;
|
||||
readonly attribute DOMString mediaSource;
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/Hal.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "MozMtpServer.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsString.h"
|
||||
@ -35,6 +36,7 @@
|
||||
#include "VolumeManager.h"
|
||||
|
||||
using namespace mozilla::hal;
|
||||
USING_MTP_NAMESPACE
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
@ -69,6 +71,7 @@ using namespace mozilla::hal;
|
||||
|
||||
#define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions"
|
||||
#define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage"
|
||||
#define ICS_SYS_MTP_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mtp"
|
||||
#define ICS_SYS_USB_STATE "/sys/devices/virtual/android_usb/android0/state"
|
||||
|
||||
#define USE_DEBUG 0
|
||||
@ -229,6 +232,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void StartMtpServer();
|
||||
void StopMtpServer();
|
||||
|
||||
void UpdateState();
|
||||
|
||||
const char* ModeStr(int32_t aMode)
|
||||
@ -347,6 +353,7 @@ private:
|
||||
};
|
||||
|
||||
static StaticRefPtr<AutoMounter> sAutoMounter;
|
||||
static StaticRefPtr<MozMtpServer> sMozMtpServer;
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
@ -398,6 +405,25 @@ AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AutoMounter::StartMtpServer()
|
||||
{
|
||||
if (sMozMtpServer) {
|
||||
// Mtp Server is already running - nothing to do
|
||||
return;
|
||||
}
|
||||
LOG("Starting MtpServer");
|
||||
sMozMtpServer = new MozMtpServer();
|
||||
sMozMtpServer->Run();
|
||||
}
|
||||
|
||||
void
|
||||
AutoMounter::StopMtpServer()
|
||||
{
|
||||
LOG("Stopping MtpServer");
|
||||
sMozMtpServer = nullptr;
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
void
|
||||
@ -441,22 +467,23 @@ AutoMounter::UpdateState()
|
||||
|
||||
bool umsAvail = false;
|
||||
bool umsEnabled = false;
|
||||
bool mtpAvail = false;
|
||||
bool mtpEnabled = false;
|
||||
|
||||
if (access(ICS_SYS_USB_FUNCTIONS, F_OK) == 0) {
|
||||
char functionsStr[60];
|
||||
if (!ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) {
|
||||
ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno));
|
||||
functionsStr[0] = '\0';
|
||||
}
|
||||
umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
|
||||
if (umsAvail) {
|
||||
char functionsStr[60];
|
||||
if (ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) {
|
||||
umsEnabled = strstr(functionsStr, "mass_storage") != nullptr;
|
||||
} else {
|
||||
ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno));
|
||||
umsEnabled = false;
|
||||
}
|
||||
} else {
|
||||
umsEnabled = false;
|
||||
umsEnabled = strstr(functionsStr, "mass_storage") != nullptr;
|
||||
}
|
||||
mtpAvail = (access(ICS_SYS_MTP_DIRECTORY, F_OK) == 0);
|
||||
if (mtpAvail) {
|
||||
mtpEnabled = strstr(functionsStr, "mtp") != nullptr;
|
||||
}
|
||||
} else {
|
||||
umsAvail = ReadSysFile(GB_SYS_UMS_ENABLE, &umsEnabled);
|
||||
}
|
||||
|
||||
bool usbCablePluggedIn = IsUsbCablePluggedIn();
|
||||
@ -469,9 +496,19 @@ AutoMounter::UpdateState()
|
||||
}
|
||||
}
|
||||
|
||||
bool tryToShare = (umsAvail && umsEnabled && enabled && usbCablePluggedIn);
|
||||
LOG("UpdateState: umsAvail:%d umsEnabled:%d mode:%d usbCablePluggedIn:%d tryToShare:%d",
|
||||
umsAvail, umsEnabled, mMode, usbCablePluggedIn, tryToShare);
|
||||
bool tryToShare = (((umsAvail && umsEnabled) || (mtpAvail && mtpEnabled))
|
||||
&& enabled && usbCablePluggedIn);
|
||||
LOG("UpdateState: ums:%d%d mtp:%d%d mode:%d usbCablePluggedIn:%d tryToShare:%d",
|
||||
umsAvail, umsEnabled, mtpAvail, mtpEnabled, mMode, usbCablePluggedIn, tryToShare);
|
||||
|
||||
if (mtpAvail && mtpEnabled) {
|
||||
if (enabled && usbCablePluggedIn) {
|
||||
StartMtpServer();
|
||||
} else {
|
||||
StopMtpServer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool filesOpen = false;
|
||||
static unsigned filesOpenDelayCount = 0;
|
||||
@ -902,6 +939,9 @@ AutoMounterUnmountVolume(const nsCString& aVolumeName)
|
||||
void
|
||||
ShutdownAutoMounter()
|
||||
{
|
||||
if (sAutoMounter) {
|
||||
sAutoMounter->StopMtpServer();
|
||||
}
|
||||
sAutoMounterSetting = nullptr;
|
||||
sUsbCableObserver = nullptr;
|
||||
|
||||
|
44
dom/system/gonk/MozMtpCommon.h
Normal file
44
dom/system/gonk/MozMtpCommon.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* -*- 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_system_mozmtpcommon_h__
|
||||
#define mozilla_system_mozmtpcommon_h__
|
||||
|
||||
#include "mozilla/Types.h"
|
||||
#include <android/log.h>
|
||||
|
||||
#define MTP_LOG(msg, ...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "MozMtp", \
|
||||
"%s: " msg, __FUNCTION__, ##__VA_ARGS__) \
|
||||
|
||||
#define MTP_ERR(msg, ...) \
|
||||
__android_log_print(ANDROID_LOG_ERROR, "MozMtp", \
|
||||
"%s: " msg, __FUNCTION__, ##__VA_ARGS__) \
|
||||
|
||||
#define BEGIN_MTP_NAMESPACE \
|
||||
namespace mozilla { namespace system { namespace mtp {
|
||||
#define END_MTP_NAMESPACE \
|
||||
} /* namespace mtp */ } /* namespace system */ } /* namespace mozilla */
|
||||
#define USING_MTP_NAMESPACE \
|
||||
using namespace mozilla::system::mtp;
|
||||
|
||||
namespace android {
|
||||
class MOZ_EXPORT MtpServer;
|
||||
class MOZ_EXPORT MtpStorage;
|
||||
class MOZ_EXPORT MtpDatabase;
|
||||
class MOZ_EXPORT MtpDataPacket;
|
||||
class MOZ_EXPORT MtpProperty;
|
||||
}
|
||||
|
||||
#include <mtp.h>
|
||||
#include <MtpDatabase.h>
|
||||
#include <MtpObjectInfo.h>
|
||||
#include <MtpProperty.h>
|
||||
#include <MtpServer.h>
|
||||
#include <MtpStorage.h>
|
||||
#include <MtpTypes.h>
|
||||
|
||||
#endif // mozilla_system_mtpcommon_h__
|
792
dom/system/gonk/MozMtpDatabase.cpp
Normal file
792
dom/system/gonk/MozMtpDatabase.cpp
Normal file
@ -0,0 +1,792 @@
|
||||
/* -*- 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 "MozMtpDatabase.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "prio.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <libgen.h>
|
||||
|
||||
using namespace android;
|
||||
using namespace mozilla;
|
||||
|
||||
namespace mozilla {
|
||||
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
|
||||
}
|
||||
|
||||
BEGIN_MTP_NAMESPACE
|
||||
|
||||
static const char *
|
||||
ObjectPropertyAsStr(MtpObjectProperty aProperty)
|
||||
{
|
||||
switch (aProperty) {
|
||||
case MTP_PROPERTY_STORAGE_ID: return "MTP_PROPERTY_STORAGE_ID";
|
||||
case MTP_PROPERTY_OBJECT_FORMAT: return "MTP_PROPERTY_OBJECT_FORMAT";
|
||||
case MTP_PROPERTY_OBJECT_SIZE: return "MTP_PROPERTY_OBJECT_SIZE";
|
||||
case MTP_PROPERTY_WIDTH: return "MTP_PROPERTY_WIDTH";
|
||||
case MTP_PROPERTY_HEIGHT: return "MTP_PROPERTY_HEIGHT";
|
||||
case MTP_PROPERTY_IMAGE_BIT_DEPTH: return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
|
||||
case MTP_PROPERTY_DISPLAY_NAME: return "MTP_PROPERTY_DISPLAY_NAME";
|
||||
}
|
||||
return "MTP_PROPERTY_???";
|
||||
}
|
||||
|
||||
MozMtpDatabase::MozMtpDatabase(const char *aDir)
|
||||
{
|
||||
MTP_LOG("");
|
||||
|
||||
// We use the index into the array as the handle. Since zero isn't a valid
|
||||
// index, we stick a dummy entry there.
|
||||
|
||||
RefPtr<DbEntry> dummy;
|
||||
|
||||
mDb.AppendElement(dummy);
|
||||
|
||||
ReadVolume("sdcard", aDir);
|
||||
}
|
||||
|
||||
//virtual
|
||||
MozMtpDatabase::~MozMtpDatabase()
|
||||
{
|
||||
MTP_LOG("");
|
||||
}
|
||||
|
||||
void
|
||||
MozMtpDatabase::AddEntry(DbEntry *entry)
|
||||
{
|
||||
entry->mHandle = GetNextHandle();
|
||||
MOZ_ASSERT(mDb.Length() == entry->mHandle);
|
||||
mDb.AppendElement(entry);
|
||||
|
||||
MTP_LOG("AddEntry: Handle: 0x%08x Parent: 0x%08x Path:'%s'",
|
||||
entry->mHandle, entry->mParent, entry->mPath.get());
|
||||
}
|
||||
|
||||
TemporaryRef<MozMtpDatabase::DbEntry>
|
||||
MozMtpDatabase::GetEntry(MtpObjectHandle aHandle)
|
||||
{
|
||||
RefPtr<DbEntry> entry;
|
||||
|
||||
if (aHandle > 0 && aHandle < mDb.Length()) {
|
||||
entry = mDb[aHandle];
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void
|
||||
MozMtpDatabase::RemoveEntry(MtpObjectHandle aHandle)
|
||||
{
|
||||
if (aHandle > 0 && aHandle < mDb.Length()) {
|
||||
mDb[aHandle] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsCString
|
||||
MozMtpDatabase::BaseName(const nsCString& path)
|
||||
{
|
||||
nsCOMPtr<nsIFile> file;
|
||||
NS_NewNativeLocalFile(path, false, getter_AddRefs(file));
|
||||
if (file) {
|
||||
nsCString leafName;
|
||||
file->GetNativeLeafName(leafName);
|
||||
return leafName;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
void
|
||||
MozMtpDatabase::ParseDirectory(const char *aDir, MtpObjectHandle aParent)
|
||||
{
|
||||
ScopedCloseDir dir;
|
||||
|
||||
if (!(dir = PR_OpenDir(aDir))) {
|
||||
MTP_ERR("Unable to open directory '%s'", aDir);
|
||||
return;
|
||||
}
|
||||
|
||||
PRDirEntry* dirEntry;
|
||||
while ((dirEntry = PR_ReadDir(dir, PR_SKIP_BOTH))) {
|
||||
nsPrintfCString filename("%s/%s", aDir, dirEntry->name);
|
||||
PRFileInfo64 fileInfo;
|
||||
if (PR_GetFileInfo64(filename.get(), &fileInfo) != PR_SUCCESS) {
|
||||
MTP_ERR("Unable to retrieve file information for '%s'", filename.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
RefPtr<DbEntry> entry = new DbEntry;
|
||||
|
||||
entry->mStorageID = MTP_STORAGE_FIXED_RAM;
|
||||
entry->mParent = aParent;
|
||||
entry->mObjectName = dirEntry->name;
|
||||
entry->mDisplayName = dirEntry->name;
|
||||
entry->mPath = filename;
|
||||
entry->mDateCreated = fileInfo.creationTime;
|
||||
entry->mDateModified = fileInfo.modifyTime;
|
||||
|
||||
if (fileInfo.type == PR_FILE_FILE) {
|
||||
entry->mObjectFormat = MTP_FORMAT_DEFINED;
|
||||
//TODO: Check how 64-bit filesize are dealt with
|
||||
entry->mObjectSize = fileInfo.size;
|
||||
AddEntry(entry);
|
||||
} else if (fileInfo.type == PR_FILE_DIRECTORY) {
|
||||
entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
|
||||
entry->mObjectSize = 0;
|
||||
AddEntry(entry);
|
||||
ParseDirectory(filename.get(), entry->mHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MozMtpDatabase::ReadVolume(const char *volumeName, const char *aDir)
|
||||
{
|
||||
//TODO: Add an assert re thread being run on
|
||||
|
||||
PRFileInfo fileInfo;
|
||||
|
||||
if (PR_GetFileInfo(aDir, &fileInfo) != PR_SUCCESS) {
|
||||
MTP_ERR("'%s' doesn't exist", aDir);
|
||||
return;
|
||||
}
|
||||
if (fileInfo.type != PR_FILE_DIRECTORY) {
|
||||
MTP_ERR("'%s' isn't a directory", aDir);
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<DbEntry> entry = new DbEntry;
|
||||
|
||||
entry->mStorageID = MTP_STORAGE_FIXED_RAM;
|
||||
entry->mParent = MTP_PARENT_ROOT;
|
||||
entry->mObjectName = volumeName;
|
||||
entry->mDisplayName = volumeName;
|
||||
entry->mPath = aDir;
|
||||
entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
|
||||
entry->mObjectSize = 0;
|
||||
|
||||
AddEntry(entry);
|
||||
|
||||
ParseDirectory(aDir, entry->mHandle);
|
||||
}
|
||||
|
||||
// called from SendObjectInfo to reserve a database entry for the incoming file
|
||||
//virtual
|
||||
MtpObjectHandle
|
||||
MozMtpDatabase::beginSendObject(const char* aPath,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent,
|
||||
MtpStorageID aStorageID,
|
||||
uint64_t aSize,
|
||||
time_t aModified)
|
||||
{
|
||||
if (!aParent) {
|
||||
MTP_LOG("aParent is NULL");
|
||||
return kInvalidObjectHandle;
|
||||
}
|
||||
|
||||
RefPtr<DbEntry> entry = new DbEntry;
|
||||
|
||||
entry->mStorageID = aStorageID;
|
||||
entry->mParent = aParent;
|
||||
entry->mPath = aPath;
|
||||
entry->mObjectName = BaseName(entry->mPath);
|
||||
entry->mDisplayName = entry->mObjectName;
|
||||
entry->mObjectFormat = aFormat;
|
||||
entry->mObjectSize = aSize;
|
||||
|
||||
AddEntry(entry);
|
||||
|
||||
MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s'", entry->mHandle, aParent, aPath);
|
||||
|
||||
return entry->mHandle;
|
||||
}
|
||||
|
||||
// called to report success or failure of the SendObject file transfer
|
||||
// success should signal a notification of the new object's creation,
|
||||
// failure should remove the database entry created in beginSendObject
|
||||
|
||||
//virtual
|
||||
void
|
||||
MozMtpDatabase::endSendObject(const char* aPath,
|
||||
MtpObjectHandle aHandle,
|
||||
MtpObjectFormat aFormat,
|
||||
bool succeeded)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
|
||||
if (!succeeded) {
|
||||
RemoveEntry(aHandle);
|
||||
}
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpObjectHandleList*
|
||||
MozMtpDatabase::getObjectList(MtpStorageID aStorageID,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent)
|
||||
{
|
||||
MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x",
|
||||
aStorageID, aFormat, aParent);
|
||||
|
||||
//TODO: Optimize
|
||||
|
||||
ScopedDeletePtr<MtpObjectHandleList> list;
|
||||
|
||||
list = new MtpObjectHandleList();
|
||||
|
||||
DbArray::size_type numEntries = mDb.Length();
|
||||
DbArray::index_type entryIndex;
|
||||
for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
|
||||
RefPtr<DbEntry> entry = mDb[entryIndex];
|
||||
if (entry->mParent == aParent) {
|
||||
list->push(entry->mHandle);
|
||||
}
|
||||
}
|
||||
return list.forget();
|
||||
}
|
||||
|
||||
//virtual
|
||||
int
|
||||
MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent)
|
||||
{
|
||||
MTP_LOG("");
|
||||
|
||||
return mDb.Length() - 1;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpObjectFormatList*
|
||||
MozMtpDatabase::getSupportedPlaybackFormats()
|
||||
{
|
||||
static const uint16_t init_data[] = {MTP_FORMAT_PNG};
|
||||
|
||||
MtpObjectFormatList *list = new MtpObjectFormatList();
|
||||
list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
|
||||
|
||||
MTP_LOG("returning MTP_FORMAT_PNG");
|
||||
return list;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpObjectFormatList*
|
||||
MozMtpDatabase::getSupportedCaptureFormats()
|
||||
{
|
||||
static const uint16_t init_data[] = {MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG};
|
||||
|
||||
MtpObjectFormatList *list = new MtpObjectFormatList();
|
||||
list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
|
||||
MTP_LOG("returning MTP_FORMAT_PNG");
|
||||
return list;
|
||||
}
|
||||
|
||||
static const MtpObjectProperty sSupportedObjectProperties[] =
|
||||
{
|
||||
MTP_PROPERTY_STORAGE_ID,
|
||||
MTP_PROPERTY_PARENT_OBJECT,
|
||||
MTP_PROPERTY_OBJECT_FORMAT,
|
||||
MTP_PROPERTY_OBJECT_SIZE,
|
||||
MTP_PROPERTY_OBJECT_FILE_NAME, // just the filename - no directory
|
||||
MTP_PROPERTY_PROTECTION_STATUS, // UINT16 - always 0
|
||||
MTP_PROPERTY_DATE_MODIFIED,
|
||||
MTP_PROPERTY_DATE_ADDED,
|
||||
};
|
||||
|
||||
//virtual
|
||||
MtpObjectPropertyList*
|
||||
MozMtpDatabase::getSupportedObjectProperties(MtpObjectFormat aFormat)
|
||||
{
|
||||
MTP_LOG("");
|
||||
MtpObjectPropertyList *list = new MtpObjectPropertyList();
|
||||
list->appendArray(sSupportedObjectProperties,
|
||||
MOZ_ARRAY_LENGTH(sSupportedObjectProperties));
|
||||
return list;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpDevicePropertyList*
|
||||
MozMtpDatabase::getSupportedDeviceProperties()
|
||||
{
|
||||
MTP_LOG("");
|
||||
static const uint16_t init_data[] = { MTP_DEVICE_PROPERTY_UNDEFINED };
|
||||
|
||||
MtpDevicePropertyList *list = new MtpDevicePropertyList();
|
||||
list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
|
||||
return list;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::getObjectPropertyValue(MtpObjectHandle aHandle,
|
||||
MtpObjectProperty aProperty,
|
||||
MtpDataPacket& aPacket)
|
||||
{
|
||||
RefPtr<DbEntry> entry = GetEntry(aHandle);
|
||||
if (!entry) {
|
||||
MTP_ERR("Invalid Handle: 0x%08x", aHandle);
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
}
|
||||
|
||||
MTP_LOG("Handle: 0x%08x '%s' Property: %s 0x%08x",
|
||||
aHandle, entry->mDisplayName.get(), ObjectPropertyAsStr(aProperty), aProperty);
|
||||
|
||||
switch (aProperty)
|
||||
{
|
||||
case MTP_PROPERTY_STORAGE_ID: aPacket.putUInt32(entry->mStorageID); break;
|
||||
case MTP_PROPERTY_PARENT_OBJECT: aPacket.putUInt32(entry->mParent); break;
|
||||
case MTP_PROPERTY_OBJECT_FORMAT: aPacket.putUInt32(entry->mObjectFormat); break;
|
||||
case MTP_PROPERTY_OBJECT_SIZE: aPacket.putUInt32(entry->mObjectSize); break;
|
||||
case MTP_PROPERTY_DISPLAY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
|
||||
|
||||
default:
|
||||
MTP_LOG("Invalid Property: 0x%08x", aProperty);
|
||||
return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
|
||||
}
|
||||
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle,
|
||||
MtpObjectProperty aProperty,
|
||||
MtpDataPacket& aPacket)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
|
||||
return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty,
|
||||
MtpDataPacket& aPacket)
|
||||
{
|
||||
MTP_LOG("(GENERAL ERROR)");
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::setDevicePropertyValue(MtpDeviceProperty aProperty,
|
||||
MtpDataPacket& aPacket)
|
||||
{
|
||||
MTP_LOG("(NOT SUPPORTED)");
|
||||
return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::resetDeviceProperty(MtpDeviceProperty aProperty)
|
||||
{
|
||||
MTP_LOG("(NOT SUPPORTED)");
|
||||
return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void
|
||||
MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType,
|
||||
uint32_t aMatchField1,
|
||||
uint32_t aMatchField2,
|
||||
DbArray &result)
|
||||
{
|
||||
DbArray::size_type numEntries = mDb.Length();
|
||||
DbArray::index_type entryIdx;
|
||||
RefPtr<DbEntry> entry;
|
||||
|
||||
result.Clear();
|
||||
|
||||
switch (aMatchType) {
|
||||
|
||||
case MatchAll:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
if (mDb[entryIdx]) {
|
||||
result.AppendElement(mDb[entryIdx]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MatchHandle:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
entry = mDb[entryIdx];
|
||||
if (entry && entry->mHandle == aMatchField1) {
|
||||
result.AppendElement(entry);
|
||||
// Handles are unique - return the one that we found.
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MatchParent:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
entry = mDb[entryIdx];
|
||||
if (entry && entry->mParent == aMatchField1) {
|
||||
result.AppendElement(entry);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MatchFormat:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
entry = mDb[entryIdx];
|
||||
if (entry && entry->mObjectFormat == aMatchField1) {
|
||||
result.AppendElement(entry);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MatchHandleFormat:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
entry = mDb[entryIdx];
|
||||
if (entry && entry->mHandle == aMatchField1) {
|
||||
if (entry->mObjectFormat == aMatchField2) {
|
||||
result.AppendElement(entry);
|
||||
}
|
||||
// Only 1 entry can match my aHandle. So we can return early.
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MatchParentFormat:
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
entry = mDb[entryIdx];
|
||||
if (entry && entry->mParent == aMatchField1 && entry->mObjectFormat == aMatchField2) {
|
||||
result.AppendElement(entry);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(!"Invalid MatchType");
|
||||
}
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle,
|
||||
uint32_t aFormat,
|
||||
uint32_t aProperty,
|
||||
int aGroupCode,
|
||||
int aDepth,
|
||||
MtpDataPacket& aPacket)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d (NOT SUPPORTED)",
|
||||
aHandle, aFormat, aProperty, aGroupCode, aDepth);
|
||||
|
||||
if (aDepth > 1) {
|
||||
return MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED;
|
||||
}
|
||||
if (aGroupCode != 0) {
|
||||
return MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED;
|
||||
}
|
||||
|
||||
MatchType matchType = MatchAll;
|
||||
uint32_t matchField1 = 0;
|
||||
uint32_t matchField2 = 0;
|
||||
|
||||
// aHandle == 0 implies all objects at the root level
|
||||
// further specificed by aFormat and/or aDepth
|
||||
|
||||
if (aFormat == 0) {
|
||||
if (aHandle == 0xffffffff) {
|
||||
// select all objects
|
||||
matchType = MatchAll;
|
||||
} else {
|
||||
if (aDepth == 1) {
|
||||
// select objects whose Parent matches aHandle
|
||||
matchType = MatchParent;
|
||||
matchField1 = aHandle;
|
||||
} else {
|
||||
// select object whose handle matches aHandle
|
||||
matchType = MatchHandle;
|
||||
matchField1 = aHandle;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (aHandle == 0xffffffff) {
|
||||
// select all objects whose format matches aFormat
|
||||
matchType = MatchFormat;
|
||||
matchField1 = aFormat;
|
||||
} else {
|
||||
if (aDepth == 1) {
|
||||
// select objects whose Parent is aHandle and format matches aFormat
|
||||
matchType = MatchParentFormat;
|
||||
matchField1 = aHandle;
|
||||
matchField2 = aFormat;
|
||||
} else {
|
||||
// select objects whose handle is aHandle and format matches aFormat
|
||||
matchType = MatchHandleFormat;
|
||||
matchField1 = aHandle;
|
||||
matchField2 = aFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DbArray result;
|
||||
QueryEntries(matchType, matchField1, matchField2, result);
|
||||
|
||||
const MtpObjectProperty *objectPropertyList;
|
||||
size_t numObjectProperties = 0;
|
||||
MtpObjectProperty objectProperty;
|
||||
|
||||
if (aProperty == 0xffffffff) {
|
||||
// return all supported properties
|
||||
numObjectProperties = MOZ_ARRAY_LENGTH(sSupportedObjectProperties);
|
||||
objectPropertyList = sSupportedObjectProperties;
|
||||
} else {
|
||||
// return property indicated by aProperty
|
||||
numObjectProperties = 1;
|
||||
objectProperty = aProperty;
|
||||
objectPropertyList = &objectProperty;
|
||||
}
|
||||
|
||||
DbArray::size_type numEntries = result.Length();
|
||||
DbArray::index_type entryIdx;
|
||||
|
||||
aPacket.putUInt32(numEntries);
|
||||
for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
|
||||
RefPtr<DbEntry> entry = result[entryIdx];
|
||||
|
||||
for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
|
||||
aPacket.putUInt32(entry->mHandle);
|
||||
MtpObjectProperty prop = objectPropertyList[propertyIdx];
|
||||
aPacket.putUInt16(prop);
|
||||
switch (prop) {
|
||||
|
||||
case MTP_PROPERTY_STORAGE_ID:
|
||||
aPacket.putUInt16(MTP_TYPE_UINT32);
|
||||
aPacket.putUInt32(entry->mStorageID);
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_PARENT_OBJECT:
|
||||
aPacket.putUInt16(MTP_TYPE_UINT32);
|
||||
aPacket.putUInt32(entry->mParent);
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_OBJECT_FORMAT:
|
||||
aPacket.putUInt16(MTP_TYPE_UINT16);
|
||||
aPacket.putUInt16(entry->mObjectFormat);
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_OBJECT_SIZE:
|
||||
aPacket.putUInt16(MTP_TYPE_UINT64);
|
||||
aPacket.putUInt64(entry->mObjectSize);
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_OBJECT_FILE_NAME:
|
||||
aPacket.putUInt16(MTP_TYPE_STR);
|
||||
aPacket.putString(entry->mObjectName.get());
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_PROTECTION_STATUS:
|
||||
aPacket.putUInt16(MTP_TYPE_UINT16);
|
||||
aPacket.putUInt16(0); // 0 = No Protection
|
||||
break;
|
||||
|
||||
case MTP_PROPERTY_DATE_MODIFIED: {
|
||||
aPacket.putUInt16(MTP_TYPE_STR);
|
||||
PRExplodedTime explodedTime;
|
||||
PR_ExplodeTime(entry->mDateModified, PR_LocalTimeParameters, &explodedTime);
|
||||
char dateStr[20];
|
||||
PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime);
|
||||
aPacket.putString(dateStr);
|
||||
break;
|
||||
}
|
||||
|
||||
case MTP_PROPERTY_DATE_ADDED: {
|
||||
aPacket.putUInt16(MTP_TYPE_STR);
|
||||
PRExplodedTime explodedTime;
|
||||
PR_ExplodeTime(entry->mDateCreated, PR_LocalTimeParameters, &explodedTime);
|
||||
char dateStr[20];
|
||||
PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime);
|
||||
aPacket.putString(dateStr);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
MTP_ERR("Unrecognized property code: %u", prop);
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle,
|
||||
MtpObjectInfo& aInfo)
|
||||
{
|
||||
RefPtr<DbEntry> entry = GetEntry(aHandle);
|
||||
if (!entry) {
|
||||
MTP_ERR("Handle 0x%08x is invalid", aHandle);
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
}
|
||||
|
||||
MTP_LOG("Handle: 0x%08x Display:'%s' Object:'%s'", aHandle, entry->mDisplayName.get(), entry->mObjectName.get());
|
||||
|
||||
aInfo.mHandle = aHandle;
|
||||
aInfo.mStorageID = entry->mStorageID;
|
||||
aInfo.mFormat = entry->mObjectFormat;
|
||||
aInfo.mProtectionStatus = 0x0;
|
||||
aInfo.mCompressedSize = 0;
|
||||
aInfo.mThumbFormat = entry->mObjectFormat;
|
||||
aInfo.mThumbCompressedSize = 20*20*4;
|
||||
aInfo.mThumbPixWidth = 20;
|
||||
aInfo.mThumbPixHeight =20;
|
||||
aInfo.mImagePixWidth = 20;
|
||||
aInfo.mImagePixHeight = 20;
|
||||
aInfo.mImagePixDepth = 4;
|
||||
aInfo.mParent = entry->mParent;
|
||||
aInfo.mAssociationType = 0;
|
||||
aInfo.mAssociationDesc = 0;
|
||||
aInfo.mSequenceNumber = 0;
|
||||
aInfo.mName = ::strdup(entry->mObjectName.get());
|
||||
aInfo.mDateCreated = 0;
|
||||
aInfo.mDateModified = 0;
|
||||
aInfo.mKeywords = ::strdup("fxos,touch");
|
||||
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
//virtual
|
||||
void*
|
||||
MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
|
||||
|
||||
aOutThumbSize = 0;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::getObjectFilePath(MtpObjectHandle aHandle,
|
||||
MtpString& aOutFilePath,
|
||||
int64_t& aOutFileLength,
|
||||
MtpObjectFormat& aOutFormat)
|
||||
{
|
||||
RefPtr<DbEntry> entry = GetEntry(aHandle);
|
||||
if (!entry) {
|
||||
MTP_ERR("Handle 0x%08x is invalid", aHandle);
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
}
|
||||
|
||||
MTP_LOG("Handle: 0x%08x FilePath: '%s'", aHandle, entry->mPath.get());
|
||||
|
||||
aOutFilePath = entry->mPath.get();
|
||||
aOutFileLength = entry->mObjectSize;
|
||||
aOutFormat = entry->mObjectFormat;
|
||||
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::deleteFile(MtpObjectHandle aHandle)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
|
||||
|
||||
//TODO
|
||||
|
||||
return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::moveFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
|
||||
|
||||
// change parent
|
||||
|
||||
return MTP_RESPONSE_OK
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::copyFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
|
||||
|
||||
// duplicate DbEntry
|
||||
// change parent
|
||||
|
||||
return MTP_RESPONSE_OK
|
||||
}
|
||||
#endif
|
||||
|
||||
//virtual
|
||||
MtpObjectHandleList*
|
||||
MozMtpDatabase::getObjectReferences(MtpObjectHandle aHandle)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpResponseCode
|
||||
MozMtpDatabase::setObjectReferences(MtpObjectHandle aHandle,
|
||||
MtpObjectHandleList* aReferences)
|
||||
{
|
||||
MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
|
||||
return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpProperty*
|
||||
MozMtpDatabase::getObjectPropertyDesc(MtpObjectProperty aProperty,
|
||||
MtpObjectFormat aFormat)
|
||||
{
|
||||
MTP_LOG("Property: %s 0x%08x", ObjectPropertyAsStr(aProperty), aProperty);
|
||||
|
||||
// TODO: Perhaps Filesize should be 64-bit?
|
||||
|
||||
MtpProperty* result = nullptr;
|
||||
switch (aProperty)
|
||||
{
|
||||
case MTP_PROPERTY_STORAGE_ID: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_OBJECT_FORMAT: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_OBJECT_SIZE: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_WIDTH: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_HEIGHT: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_IMAGE_BIT_DEPTH: result = new MtpProperty(aProperty, MTP_TYPE_UINT32); break;
|
||||
case MTP_PROPERTY_DISPLAY_NAME: result = new MtpProperty(aProperty, MTP_TYPE_STR); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//virtual
|
||||
MtpProperty*
|
||||
MozMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty aProperty)
|
||||
{
|
||||
MTP_LOG("(returning MTP_DEVICE_PROPERTY_UNDEFINED)");
|
||||
return new MtpProperty(MTP_DEVICE_PROPERTY_UNDEFINED, MTP_TYPE_UNDEFINED);
|
||||
}
|
||||
|
||||
//virtual
|
||||
void
|
||||
MozMtpDatabase::sessionStarted()
|
||||
{
|
||||
MTP_LOG("");
|
||||
}
|
||||
|
||||
//virtual
|
||||
void
|
||||
MozMtpDatabase::sessionEnded()
|
||||
{
|
||||
MTP_LOG("");
|
||||
}
|
||||
|
||||
END_MTP_NAMESPACE
|
161
dom/system/gonk/MozMtpDatabase.h
Normal file
161
dom/system/gonk/MozMtpDatabase.h
Normal file
@ -0,0 +1,161 @@
|
||||
/* -*- 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_system_mozmtpdatabase_h__
|
||||
#define mozilla_system_mozmtpdatabase_h__
|
||||
|
||||
#include "MozMtpCommon.h"
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
BEGIN_MTP_NAMESPACE // mozilla::system::mtp
|
||||
|
||||
using namespace android;
|
||||
|
||||
class MozMtpDatabase : public MtpDatabase
|
||||
{
|
||||
public:
|
||||
MozMtpDatabase(const char *aDir);
|
||||
virtual ~MozMtpDatabase();
|
||||
|
||||
// called from SendObjectInfo to reserve a database entry for the incoming file
|
||||
virtual MtpObjectHandle beginSendObject(const char* aPath,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent,
|
||||
MtpStorageID aStorageID,
|
||||
uint64_t aSize,
|
||||
time_t aModified);
|
||||
|
||||
// called to report success or failure of the SendObject file transfer
|
||||
// success should signal a notification of the new object's creation,
|
||||
// failure should remove the database entry created in beginSendObject
|
||||
virtual void endSendObject(const char* aPath,
|
||||
MtpObjectHandle aHandle,
|
||||
MtpObjectFormat aFormat,
|
||||
bool aSucceeded);
|
||||
|
||||
virtual MtpObjectHandleList* getObjectList(MtpStorageID aStorageID,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent);
|
||||
|
||||
virtual int getNumObjects(MtpStorageID aStorageID,
|
||||
MtpObjectFormat aFormat,
|
||||
MtpObjectHandle aParent);
|
||||
|
||||
virtual MtpObjectFormatList* getSupportedPlaybackFormats();
|
||||
|
||||
virtual MtpObjectFormatList* getSupportedCaptureFormats();
|
||||
|
||||
virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat aFormat);
|
||||
|
||||
virtual MtpDevicePropertyList* getSupportedDeviceProperties();
|
||||
|
||||
virtual MtpResponseCode getObjectPropertyValue(MtpObjectHandle aHandle,
|
||||
MtpObjectProperty aProperty,
|
||||
MtpDataPacket& aPacket);
|
||||
|
||||
virtual MtpResponseCode setObjectPropertyValue(MtpObjectHandle aHandle,
|
||||
MtpObjectProperty aProperty,
|
||||
MtpDataPacket& aPacket);
|
||||
|
||||
virtual MtpResponseCode getDevicePropertyValue(MtpDeviceProperty aProperty,
|
||||
MtpDataPacket& aPacket);
|
||||
|
||||
virtual MtpResponseCode setDevicePropertyValue(MtpDeviceProperty aProperty,
|
||||
MtpDataPacket& aPacket);
|
||||
|
||||
virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty aProperty);
|
||||
|
||||
virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle aHandle,
|
||||
uint32_t aFormat,
|
||||
uint32_t aProperty,
|
||||
int aGroupCode,
|
||||
int aDepth,
|
||||
MtpDataPacket& aPacket);
|
||||
|
||||
virtual MtpResponseCode getObjectInfo(MtpObjectHandle aHandle,
|
||||
MtpObjectInfo& aInfo);
|
||||
|
||||
virtual void* getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize);
|
||||
|
||||
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle aHandle,
|
||||
MtpString& aOutFilePath,
|
||||
int64_t& aOutFileLength,
|
||||
MtpObjectFormat& aOutFormat);
|
||||
|
||||
virtual MtpResponseCode deleteFile(MtpObjectHandle aHandle);
|
||||
|
||||
virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle aHandle);
|
||||
|
||||
virtual MtpResponseCode setObjectReferences(MtpObjectHandle aHandle,
|
||||
MtpObjectHandleList* aReferences);
|
||||
|
||||
virtual MtpProperty* getObjectPropertyDesc(MtpObjectProperty aProperty,
|
||||
MtpObjectFormat aFormat);
|
||||
|
||||
virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty aProperty);
|
||||
|
||||
virtual void sessionStarted();
|
||||
|
||||
virtual void sessionEnded();
|
||||
|
||||
private:
|
||||
|
||||
struct DbEntry
|
||||
{
|
||||
NS_INLINE_DECL_REFCOUNTING(DbEntry)
|
||||
|
||||
MtpObjectHandle mHandle; // uint32_t
|
||||
MtpStorageID mStorageID; // uint32_t
|
||||
nsCString mObjectName;
|
||||
MtpObjectFormat mObjectFormat; // uint16_t
|
||||
MtpObjectHandle mParent; // uint32_t
|
||||
uint64_t mObjectSize;
|
||||
nsCString mDisplayName;
|
||||
nsCString mPath;
|
||||
PRTime mDateCreated;
|
||||
PRTime mDateModified;
|
||||
};
|
||||
typedef nsTArray<mozilla::RefPtr<DbEntry> > DbArray;
|
||||
|
||||
DbArray mDb;
|
||||
|
||||
enum MatchType
|
||||
{
|
||||
MatchAll,
|
||||
MatchHandle,
|
||||
MatchParent,
|
||||
MatchFormat,
|
||||
MatchHandleFormat,
|
||||
MatchParentFormat,
|
||||
};
|
||||
|
||||
|
||||
void AddEntry(DbEntry *aEntry);
|
||||
mozilla::TemporaryRef<DbEntry> GetEntry(MtpObjectHandle aHandle);
|
||||
void RemoveEntry(MtpObjectHandle aHandle);
|
||||
void QueryEntries(MatchType aMatchType, uint32_t aMatchField1,
|
||||
uint32_t aMatchField2, DbArray& aResult);
|
||||
|
||||
nsCString BaseName(const nsCString& aPath);
|
||||
|
||||
|
||||
MtpObjectHandle GetNextHandle()
|
||||
{
|
||||
return mDb.Length();
|
||||
}
|
||||
|
||||
void ParseDirectory(const char *aDir, MtpObjectHandle aParent);
|
||||
void ReadVolume(const char *aVolumeName, const char *aDir);
|
||||
};
|
||||
|
||||
END_MTP_NAMESPACE
|
||||
|
||||
#endif // mozilla_system_mozmtpdatabase_h__
|
80
dom/system/gonk/MozMtpServer.cpp
Normal file
80
dom/system/gonk/MozMtpServer.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
/* -*- 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 "MozMtpServer.h"
|
||||
#include "MozMtpDatabase.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace mozilla;
|
||||
USING_MTP_NAMESPACE
|
||||
|
||||
class MtpServerRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
nsresult Run()
|
||||
{
|
||||
const char *mtpUsbFilename = "/dev/mtp_usb";
|
||||
const char *productName = "FirefoxOS";
|
||||
const char *storageDir = "/storage/sdcard";
|
||||
|
||||
mFd = open(mtpUsbFilename, O_RDWR);
|
||||
if (mFd.get() < 0) {
|
||||
MTP_LOG("open of '%s' failed", mtpUsbFilename);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MTP_LOG("MozMtpServer open done, fd: %d. Start reading.", mFd.get());
|
||||
|
||||
ScopedDeletePtr<MozMtpDatabase> database;
|
||||
ScopedDeletePtr<MtpServer> server;
|
||||
ScopedDeletePtr<MtpStorage> storage;
|
||||
|
||||
database = new MozMtpDatabase(storageDir);
|
||||
server = new MtpServer(mFd.get(), database, false, 1023, 0664, 0775);
|
||||
storage = new MtpStorage(MTP_STORAGE_FIXED_RAM, // id
|
||||
storageDir, // filePath
|
||||
productName, // description
|
||||
100uLL * 1024uLL * 1024uLL, // reserveSpace
|
||||
false, // removable
|
||||
2uLL * 1024uLL * 1024uLL * 1024uLL); // maxFileSize
|
||||
|
||||
server->addStorage(storage);
|
||||
|
||||
MTP_LOG("MozMtpServer started");
|
||||
server->run();
|
||||
MTP_LOG("MozMtpServer finished");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedClose mFd;
|
||||
};
|
||||
|
||||
void
|
||||
MozMtpServer::Run()
|
||||
{
|
||||
nsresult rv = NS_NewNamedThread("MtpServer", getter_AddRefs(mServerThread));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mServerThread);
|
||||
mServerThread->Dispatch(new MtpServerRunnable(), 0);
|
||||
}
|
32
dom/system/gonk/MozMtpServer.h
Normal file
32
dom/system/gonk/MozMtpServer.h
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/. */
|
||||
|
||||
#ifndef mozilla_system_mozmtpserver_h__
|
||||
#define mozilla_system_mozmtpserver_h__
|
||||
|
||||
#include "MozMtpCommon.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIThread.h"
|
||||
|
||||
BEGIN_MTP_NAMESPACE
|
||||
|
||||
class MozMtpServer
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(MozMtpServer)
|
||||
|
||||
void Run();
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIThread> mServerThread;
|
||||
};
|
||||
|
||||
END_MTP_NAMESPACE
|
||||
|
||||
#endif // mozilla_system_mozmtpserver_h__
|
||||
|
||||
|
@ -41,6 +41,8 @@ SOURCES += [
|
||||
'AutoMounter.cpp',
|
||||
'AutoMounterSetting.cpp',
|
||||
'GonkGPSGeolocationProvider.cpp',
|
||||
'MozMtpDatabase.cpp',
|
||||
'MozMtpServer.cpp',
|
||||
'NetworkUtils.cpp',
|
||||
'NetworkWorker.cpp',
|
||||
'nsVolume.cpp',
|
||||
@ -57,6 +59,11 @@ SOURCES += [
|
||||
'VolumeServiceTest.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['ANDROID_VERSION'] >= '17':
|
||||
CXXFLAGS += ['-I%s/frameworks/av/media/mtp' % CONFIG['ANDROID_SOURCE']]
|
||||
else:
|
||||
CXXFLAGS += ['-I%s/frameworks/base/media/mtp' % CONFIG['ANDROID_SOURCE']]
|
||||
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
|
||||
|
||||
|
@ -14,6 +14,12 @@ enum VideoFacingModeEnum {
|
||||
"right"
|
||||
};
|
||||
|
||||
enum MediaSourceEnum {
|
||||
"camera",
|
||||
"screen",
|
||||
"application"
|
||||
};
|
||||
|
||||
dictionary ConstrainLongRange {
|
||||
long min = -2147483647; // +1 works around windows compiler bug
|
||||
long max = 2147483647;
|
||||
|
@ -13,17 +13,20 @@ enum SupportedVideoConstraints {
|
||||
"width",
|
||||
"height",
|
||||
"frameRate",
|
||||
"mediaSource"
|
||||
};
|
||||
|
||||
enum SupportedAudioConstraints {
|
||||
"other"
|
||||
};
|
||||
|
||||
|
||||
dictionary MediaTrackConstraintSet {
|
||||
ConstrainLongRange width;
|
||||
ConstrainLongRange height;
|
||||
ConstrainDoubleRange frameRate;
|
||||
ConstrainVideoFacingMode facingMode;
|
||||
ConstrainMediaSource mediaSource = "camera";
|
||||
};
|
||||
|
||||
// TODO: Bug 995352 can't nest unions
|
||||
@ -31,5 +34,8 @@ dictionary MediaTrackConstraintSet {
|
||||
//typedef (double or ConstrainDoubleRange) ConstrainDouble;
|
||||
|
||||
typedef VideoFacingModeEnum ConstrainVideoFacingMode;
|
||||
typedef MediaSourceEnum ConstrainMediaSource;
|
||||
|
||||
// TODO: Bug 767924 sequences in unions
|
||||
//typedef (VideoFacingModeEnum or sequence<VideoFacingModeEnum>) ConstrainVideoFacingMode;
|
||||
//typedef (MediaSourceEnum or sequence<MediaSourceEnum>) ConstrainMediaSource;
|
@ -1146,6 +1146,7 @@ var WifiManager = (function() {
|
||||
createWaitForDriverReadyTimer(doStartWifiTethering);
|
||||
});
|
||||
} else {
|
||||
cancelWifiHotspotStatusTimer();
|
||||
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
|
||||
configuration, function(result) {
|
||||
// Should we fire a dom event if we fail to set wifi tethering ?
|
||||
|
@ -594,6 +594,18 @@ LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */)
|
||||
continue;
|
||||
}
|
||||
|
||||
matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_min_empty_chunk_count");
|
||||
if (memPrefName == matchName || (!rts && index == 12)) {
|
||||
UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MIN_EMPTY_CHUNK_COUNT);
|
||||
continue;
|
||||
}
|
||||
|
||||
matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_max_empty_chunk_count");
|
||||
if (memPrefName == matchName || (!rts && index == 13)) {
|
||||
UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MAX_EMPTY_CHUNK_COUNT);
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
nsAutoCString message("Workers don't support the 'mem.");
|
||||
message.Append(memPrefName);
|
||||
|
@ -68,7 +68,8 @@ public:
|
||||
* relative to the current scroll offset. HandleLongTapUp will always be
|
||||
* preceeded by HandleLongTap. However not all calls to HandleLongTap will
|
||||
* be followed by a HandleLongTapUp (for example, if the user drags
|
||||
* around between the long-tap and lifting their finger).
|
||||
* around between the long-tap and lifting their finger, or if content
|
||||
* notifies the APZ that the long-tap event was prevent-defaulted).
|
||||
*/
|
||||
virtual void HandleLongTapUp(const CSSPoint& aPoint,
|
||||
int32_t aModifiers,
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "FrameMetrics.h" // for FrameMetrics, etc
|
||||
#include "GestureEventListener.h" // for GestureEventListener
|
||||
#include "InputData.h" // for MultiTouchInput, etc
|
||||
#include "TouchBlockState.h" // for TouchBlockState
|
||||
#include "Units.h" // for CSSRect, CSSPoint, etc
|
||||
#include "UnitTransforms.h" // for TransformTo
|
||||
#include "base/message_loop.h" // for MessageLoop
|
||||
@ -316,14 +317,6 @@ typedef GeckoContentController::APZStateChange APZStateChange;
|
||||
* Units: ms
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default touch behavior (is used when not touch behavior is set).
|
||||
*/
|
||||
static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN |
|
||||
AllowedTouchBehavior::HORIZONTAL_PAN |
|
||||
AllowedTouchBehavior::PINCH_ZOOM |
|
||||
AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
|
||||
|
||||
/**
|
||||
* Angle from axis within which we stay axis-locked
|
||||
*/
|
||||
@ -412,6 +405,26 @@ GetFrameTime() {
|
||||
return sFrameTime;
|
||||
}
|
||||
|
||||
static PRThread* sControllerThread;
|
||||
|
||||
static void
|
||||
AssertOnControllerThread() {
|
||||
if (!AsyncPanZoomController::GetThreadAssertionsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static bool sControllerThreadDetermined = false;
|
||||
if (!sControllerThreadDetermined) {
|
||||
// Technically this may not actually pick up the correct controller thread,
|
||||
// if the first call to this method happens from a non-controller thread.
|
||||
// If the assertion below fires, it is possible that it is because
|
||||
// sControllerThread is not actually the controller thread.
|
||||
sControllerThread = PR_GetCurrentThread();
|
||||
sControllerThreadDetermined = true;
|
||||
}
|
||||
MOZ_ASSERT(sControllerThread == PR_GetCurrentThread());
|
||||
}
|
||||
|
||||
class FlingAnimation: public AsyncPanZoomAnimation {
|
||||
public:
|
||||
FlingAnimation(AsyncPanZoomController& aApzc,
|
||||
@ -697,7 +710,6 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
|
||||
mSharingFrameMetricsAcrossProcesses(false),
|
||||
mMonitor("AsyncPanZoomController"),
|
||||
mState(NOTHING),
|
||||
mContentResponseTimeoutTask(nullptr),
|
||||
mX(MOZ_THIS_IN_INITIALIZER_LIST()),
|
||||
mY(MOZ_THIS_IN_INITIALIZER_LIST()),
|
||||
mPanDirRestricted(false),
|
||||
@ -707,7 +719,7 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
|
||||
mLastAsyncScrollOffset(0, 0),
|
||||
mCurrentAsyncScrollOffset(0, 0),
|
||||
mAsyncScrollTimeoutTask(nullptr),
|
||||
mHandlingTouchQueue(false),
|
||||
mTouchBlockBalance(0),
|
||||
mTreeManager(aTreeManager),
|
||||
mScrollParentId(FrameMetrics::NULL_SCROLL_ID),
|
||||
mAPZCId(sAsyncPanZoomControllerCount++),
|
||||
@ -759,6 +771,8 @@ AsyncPanZoomController::Destroy()
|
||||
{
|
||||
CancelAnimation();
|
||||
|
||||
mTouchBlockQueue.Clear();
|
||||
|
||||
{ // scope the lock
|
||||
MonitorAutoLock lock(mRefPtrMonitor);
|
||||
mGeckoContentController = nullptr;
|
||||
@ -802,42 +816,57 @@ AsyncPanZoomController::GetTouchStartTolerance()
|
||||
}
|
||||
|
||||
nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
|
||||
if (aEvent.mInputType == MULTITOUCH_INPUT &&
|
||||
aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
|
||||
// Starting a new touch block, clear old touch block state.
|
||||
mTouchBlockState = TouchBlockState();
|
||||
AssertOnControllerThread();
|
||||
|
||||
if (aEvent.mInputType != MULTITOUCH_INPUT) {
|
||||
return HandleInputEvent(aEvent);
|
||||
}
|
||||
|
||||
// If we may have touch listeners and touch action property is enabled, we
|
||||
// enable the machinery that allows touch listeners to preventDefault any touch inputs
|
||||
// and also waits for the allowed touch behavior values to be received from the outside.
|
||||
// This should not happen unless there are actually touch listeners and touch-action property
|
||||
// enable as it introduces potentially unbounded lag because it causes a round-trip through
|
||||
// content. Usually, if content is responding in a timely fashion, this only introduces a
|
||||
// nearly constant few hundred ms of lag.
|
||||
if ((mFrameMetrics.mMayHaveTouchListeners || mFrameMetrics.mMayHaveTouchCaret) &&
|
||||
aEvent.mInputType == MULTITOUCH_INPUT &&
|
||||
(mState == NOTHING || mState == TOUCHING || IsPanningState(mState))) {
|
||||
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
|
||||
if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
|
||||
SetState(WAITING_CONTENT_RESPONSE);
|
||||
TouchBlockState* block = nullptr;
|
||||
if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
|
||||
block = StartNewTouchBlock(false);
|
||||
APZC_LOG("%p started new touch block %p\n", this, block);
|
||||
if (mFrameMetrics.mMayHaveTouchListeners || mFrameMetrics.mMayHaveTouchCaret) {
|
||||
// Content may intercept the touch events and prevent-default them. So we schedule
|
||||
// a timeout to give content time to do that.
|
||||
ScheduleContentResponseTimeout();
|
||||
} else {
|
||||
// Content won't prevent-default this, so we can just pretend like we scheduled
|
||||
// a timeout and it expired. Note that we will still receive a ContentReceivedTouch
|
||||
// callback for this block, and so we need to make sure we adjust the touch balance.
|
||||
APZC_LOG("%p not waiting for content response on block %p\n", this, block);
|
||||
mTouchBlockBalance++;
|
||||
block->TimeoutContentResponse();
|
||||
}
|
||||
} else if (mTouchBlockQueue.IsEmpty()) {
|
||||
NS_WARNING("Received a non-start touch event while no touch blocks active!");
|
||||
} else {
|
||||
// this touch is part of the most-recently created block
|
||||
block = mTouchBlockQueue.LastElement().get();
|
||||
APZC_LOG("%p received new event in block %p\n", this, block);
|
||||
}
|
||||
|
||||
if (mState == WAITING_CONTENT_RESPONSE || mHandlingTouchQueue) {
|
||||
if (aEvent.mInputType == MULTITOUCH_INPUT) {
|
||||
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
|
||||
mTouchQueue.AppendElement(multiTouchInput);
|
||||
|
||||
SetContentResponseTimer();
|
||||
}
|
||||
if (!block) {
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
return HandleInputEvent(aEvent);
|
||||
if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
|
||||
APZC_LOG("%p's current touch block is ready with preventdefault %d\n",
|
||||
this, block->IsDefaultPrevented());
|
||||
if (block->IsDefaultPrevented()) {
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
return HandleInputEvent(aEvent);
|
||||
}
|
||||
|
||||
// Otherwise, add it to the queue for the touch block
|
||||
block->AddEvent(aEvent.AsMultiTouchInput());
|
||||
return nsEventStatus_eConsumeDoDefault;
|
||||
}
|
||||
|
||||
nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
|
||||
AssertOnControllerThread();
|
||||
|
||||
nsEventStatus rv = nsEventStatus_eIgnore;
|
||||
|
||||
switch (aEvent.mInputType) {
|
||||
@ -884,6 +913,8 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent)
|
||||
|
||||
nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent)
|
||||
{
|
||||
AssertOnControllerThread();
|
||||
|
||||
nsEventStatus rv = nsEventStatus_eIgnore;
|
||||
|
||||
switch (aEvent.mInputType) {
|
||||
@ -957,7 +988,6 @@ nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent
|
||||
case CROSS_SLIDING_X:
|
||||
case CROSS_SLIDING_Y:
|
||||
case PINCHING:
|
||||
case WAITING_CONTENT_RESPONSE:
|
||||
NS_WARNING("Received impossible touch in OnTouchStart");
|
||||
break;
|
||||
default:
|
||||
@ -992,9 +1022,7 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent)
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled() &&
|
||||
(GetTouchBehavior(0) & AllowedTouchBehavior::VERTICAL_PAN) &&
|
||||
(GetTouchBehavior(0) & AllowedTouchBehavior::HORIZONTAL_PAN)) {
|
||||
if (gfxPrefs::TouchActionEnabled() && CurrentTouchBlock()->TouchActionAllowsPanningXY()) {
|
||||
// User tries to trigger a touch behavior. If allowed touch behavior is vertical pan
|
||||
// + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault
|
||||
// status immediately to trigger cancel event further. It should happen independent of
|
||||
@ -1017,7 +1045,6 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent)
|
||||
NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
|
||||
return nsEventStatus_eIgnore;
|
||||
|
||||
case WAITING_CONTENT_RESPONSE:
|
||||
case SNAP_BACK: // Should not receive a touch-move in the SNAP_BACK state
|
||||
// as touch blocks that begin in an overscrolled state
|
||||
// are ignored.
|
||||
@ -1092,7 +1119,6 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent)
|
||||
NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
|
||||
return nsEventStatus_eIgnore;
|
||||
|
||||
case WAITING_CONTENT_RESPONSE:
|
||||
case SNAP_BACK: // Should not receive a touch-move in the SNAP_BACK state
|
||||
// as touch blocks that begin in an overscrolled state
|
||||
// are ignored.
|
||||
@ -1113,7 +1139,9 @@ nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEven
|
||||
nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
|
||||
APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
|
||||
|
||||
if (!TouchActionAllowPinchZoom()) {
|
||||
// Note that there may not be a touch block at this point, if we received the
|
||||
// PinchGestureEvent directly from widget code without any touch events.
|
||||
if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
@ -1130,7 +1158,7 @@ nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEve
|
||||
nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
||||
APZC_LOG("%p got a scale in state %d\n", this, mState);
|
||||
|
||||
if (!TouchActionAllowPinchZoom()) {
|
||||
if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
@ -1213,7 +1241,7 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
||||
nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
|
||||
APZC_LOG("%p got a scale-end in state %d\n", this, mState);
|
||||
|
||||
if (!TouchActionAllowPinchZoom()) {
|
||||
if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
@ -1353,8 +1381,8 @@ nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent)
|
||||
int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
|
||||
CSSPoint geckoScreenPoint;
|
||||
if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
|
||||
SetState(WAITING_CONTENT_RESPONSE);
|
||||
SetContentResponseTimer();
|
||||
StartNewTouchBlock(true);
|
||||
ScheduleContentResponseTimeout();
|
||||
controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid());
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
@ -1391,7 +1419,7 @@ nsEventStatus AsyncPanZoomController::GenerateSingleTap(const ScreenIntPoint& aP
|
||||
NewRunnableMethod(controller.get(), &GeckoContentController::HandleSingleTap,
|
||||
geckoScreenPoint, modifiers, GetGuid()),
|
||||
0);
|
||||
mTouchBlockState.mSingleTapOccurred = true;
|
||||
CurrentTouchBlock()->SetSingleTapOccurred();
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
@ -1401,7 +1429,7 @@ nsEventStatus AsyncPanZoomController::GenerateSingleTap(const ScreenIntPoint& aP
|
||||
void AsyncPanZoomController::OnTouchEndOrCancel() {
|
||||
if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) {
|
||||
controller->NotifyAPZStateChange(
|
||||
GetGuid(), APZStateChange::EndTouch, mTouchBlockState.mSingleTapOccurred);
|
||||
GetGuid(), APZStateChange::EndTouch, CurrentTouchBlock()->SingleTapOccurred());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1409,7 +1437,7 @@ nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEven
|
||||
APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
|
||||
// If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before
|
||||
// sending event to content
|
||||
if (!(mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom())) {
|
||||
if (!(mZoomConstraints.mAllowDoubleTapZoom && CurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
|
||||
return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers);
|
||||
}
|
||||
return nsEventStatus_eIgnore;
|
||||
@ -1424,7 +1452,7 @@ nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent)
|
||||
APZC_LOG("%p got a double-tap in state %d\n", this, mState);
|
||||
nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
|
||||
if (controller) {
|
||||
if (mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom()) {
|
||||
if (mZoomConstraints.mAllowDoubleTapZoom && CurrentTouchBlock()->TouchActionAllowsDoubleTapZoom()) {
|
||||
int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
|
||||
CSSPoint geckoScreenPoint;
|
||||
if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
|
||||
@ -1451,10 +1479,10 @@ const ScreenPoint AsyncPanZoomController::GetVelocityVector() {
|
||||
return ScreenPoint(mX.GetVelocity(), mY.GetVelocity());
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) {
|
||||
void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle) {
|
||||
// Handling of cross sliding will need to be added in this method after touch-action released
|
||||
// enabled by default.
|
||||
if ((aBehavior & AllowedTouchBehavior::VERTICAL_PAN) && (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN)) {
|
||||
if (CurrentTouchBlock()->TouchActionAllowsPanningXY()) {
|
||||
if (mX.CanScrollNow() && mY.CanScrollNow()) {
|
||||
if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) {
|
||||
mY.SetAxisLocked(true);
|
||||
@ -1470,7 +1498,7 @@ void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBe
|
||||
} else {
|
||||
SetState(NOTHING);
|
||||
}
|
||||
} else if (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN) {
|
||||
} else if (CurrentTouchBlock()->TouchActionAllowsPanningX()) {
|
||||
// Using bigger angle for panning to keep behavior consistent
|
||||
// with IE.
|
||||
if (IsCloseToHorizontal(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) {
|
||||
@ -1482,7 +1510,7 @@ void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBe
|
||||
// requires it.
|
||||
SetState(NOTHING);
|
||||
}
|
||||
} else if (aBehavior & AllowedTouchBehavior::VERTICAL_PAN) {
|
||||
} else if (CurrentTouchBlock()->TouchActionAllowsPanningY()) {
|
||||
if (IsCloseToVertical(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) {
|
||||
mX.SetAxisLocked(true);
|
||||
SetState(PANNING_LOCKED_Y);
|
||||
@ -1561,7 +1589,7 @@ nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent
|
||||
angle = fabs(angle); // range [0, pi]
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
HandlePanningWithTouchAction(angle, GetTouchBehavior(0));
|
||||
HandlePanningWithTouchAction(angle);
|
||||
} else {
|
||||
if (GetAxisLockMode() == FREE) {
|
||||
SetState(PANNING);
|
||||
@ -2458,90 +2486,154 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
|
||||
mTouchBlockState.mPreventDefaultSet = true;
|
||||
mTouchBlockState.mPreventDefault = aPreventDefault;
|
||||
CheckContentResponse();
|
||||
void
|
||||
AsyncPanZoomController::ScheduleContentResponseTimeout() {
|
||||
APZC_LOG("%p scheduling content response timeout\n", this);
|
||||
PostDelayedTask(
|
||||
NewRunnableMethod(this, &AsyncPanZoomController::ContentResponseTimeout),
|
||||
gfxPrefs::APZContentResponseTimeout());
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::CheckContentResponse() {
|
||||
bool canProceedToTouchState = true;
|
||||
void
|
||||
AsyncPanZoomController::ContentResponseTimeout() {
|
||||
AssertOnControllerThread();
|
||||
|
||||
if (mFrameMetrics.mMayHaveTouchListeners ||
|
||||
mFrameMetrics.mMayHaveTouchCaret) {
|
||||
canProceedToTouchState &= mTouchBlockState.mPreventDefaultSet;
|
||||
}
|
||||
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
canProceedToTouchState &= mTouchBlockState.mAllowedTouchBehaviorSet;
|
||||
}
|
||||
|
||||
if (!canProceedToTouchState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mContentResponseTimeoutTask) {
|
||||
mContentResponseTimeoutTask->Cancel();
|
||||
mContentResponseTimeoutTask = nullptr;
|
||||
}
|
||||
|
||||
if (mState == WAITING_CONTENT_RESPONSE) {
|
||||
if (!mTouchBlockState.mPreventDefault) {
|
||||
SetState(NOTHING);
|
||||
}
|
||||
|
||||
mHandlingTouchQueue = true;
|
||||
|
||||
while (!mTouchQueue.IsEmpty()) {
|
||||
if (!mTouchBlockState.mPreventDefault) {
|
||||
HandleInputEvent(mTouchQueue[0]);
|
||||
}
|
||||
|
||||
if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_END ||
|
||||
mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_CANCEL) {
|
||||
mTouchQueue.RemoveElementAt(0);
|
||||
mTouchBlockBalance++;
|
||||
APZC_LOG("%p got a content response timeout; balance %d\n", this, mTouchBlockBalance);
|
||||
if (mTouchBlockBalance > 0) {
|
||||
// Find the first touch block in the queue that hasn't already received
|
||||
// the content response timeout callback, and notify it.
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||
if (mTouchBlockQueue[i]->TimeoutContentResponse()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
mTouchQueue.RemoveElementAt(0);
|
||||
}
|
||||
|
||||
mHandlingTouchQueue = false;
|
||||
if (found) {
|
||||
ProcessPendingInputBlocks();
|
||||
} else {
|
||||
NS_WARNING("APZC received more ContentResponseTimeout calls than it has unprocessed touch blocks\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncPanZoomController::TouchActionAllowPinchZoom() {
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
// Pointer events specification implies all touch points to allow zoom
|
||||
// to perform it.
|
||||
for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) {
|
||||
if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) {
|
||||
return false;
|
||||
void
|
||||
AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
|
||||
AssertOnControllerThread();
|
||||
|
||||
mTouchBlockBalance--;
|
||||
APZC_LOG("%p got a content response; balance %d\n", this, mTouchBlockBalance);
|
||||
if (mTouchBlockBalance < 0) {
|
||||
// Find the first touch block in the queue that hasn't already received
|
||||
// its response from content, and notify it.
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||
if (mTouchBlockQueue[i]->SetContentResponse(aPreventDefault)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
ProcessPendingInputBlocks();
|
||||
} else {
|
||||
NS_WARNING("APZC received more ContentReceivedTouch calls than it has unprocessed touch blocks\n");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsyncPanZoomController::TouchActionAllowDoubleTapZoom() {
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) {
|
||||
if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) {
|
||||
return false;
|
||||
void
|
||||
AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
||||
AssertOnControllerThread();
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||
if (mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
if (found) {
|
||||
ProcessPendingInputBlocks();
|
||||
} else {
|
||||
NS_WARNING("APZC received more SetAllowedTouchBehavior calls than it has unprocessed touch blocks\n");
|
||||
}
|
||||
}
|
||||
|
||||
AsyncPanZoomController::TouchBehaviorFlags
|
||||
AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) {
|
||||
if (touchIndex < mTouchBlockState.mAllowedTouchBehaviors.Length()) {
|
||||
return mTouchBlockState.mAllowedTouchBehaviors[touchIndex];
|
||||
void
|
||||
AsyncPanZoomController::ProcessPendingInputBlocks() {
|
||||
AssertOnControllerThread();
|
||||
|
||||
while (true) {
|
||||
TouchBlockState* curBlock = CurrentTouchBlock();
|
||||
if (!curBlock->IsReadyForHandling()) {
|
||||
break;
|
||||
}
|
||||
|
||||
APZC_LOG("%p processing input block %p; preventDefault %d\n",
|
||||
this, curBlock, curBlock->IsDefaultPrevented());
|
||||
if (curBlock->IsDefaultPrevented()) {
|
||||
SetState(NOTHING);
|
||||
curBlock->DropEvents();
|
||||
} else {
|
||||
while (curBlock->HasEvents()) {
|
||||
HandleInputEvent(curBlock->RemoveFirstEvent());
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(!curBlock->HasEvents());
|
||||
|
||||
if (mTouchBlockQueue.Length() == 1) {
|
||||
// If |curBlock| is the only touch block in the queue, then it is still
|
||||
// active and we cannot remove it yet. We only know that a touch block is
|
||||
// over when we start the next one. This block will be removed by the code
|
||||
// in StartNewTouchBlock, where new touch blocks are added.
|
||||
break;
|
||||
}
|
||||
|
||||
// If we get here, we know there are more touch blocks in the queue after
|
||||
// |curBlock|, so we can remove |curBlock| and try to process the next one.
|
||||
APZC_LOG("%p discarding depleted touch block %p\n", this, curBlock);
|
||||
mTouchBlockQueue.RemoveElementAt(0);
|
||||
}
|
||||
return DefaultTouchBehavior;
|
||||
}
|
||||
|
||||
TouchBlockState*
|
||||
AsyncPanZoomController::StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent)
|
||||
{
|
||||
TouchBlockState* newBlock = new TouchBlockState();
|
||||
if (gfxPrefs::TouchActionEnabled() && aCopyAllowedTouchBehaviorFromCurrent) {
|
||||
newBlock->CopyAllowedTouchBehaviorsFrom(*CurrentTouchBlock());
|
||||
}
|
||||
|
||||
// We're going to start a new block, so clear out any depleted blocks at the head of the queue.
|
||||
// See corresponding comment in ProcessPendingInputBlocks.
|
||||
while (!mTouchBlockQueue.IsEmpty()) {
|
||||
if (mTouchBlockQueue[0]->IsReadyForHandling() && !mTouchBlockQueue[0]->HasEvents()) {
|
||||
APZC_LOG("%p discarding depleted touch block %p\n", this, mTouchBlockQueue[0]);
|
||||
mTouchBlockQueue.RemoveElementAt(0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new block to the queue.
|
||||
mTouchBlockQueue.AppendElement(newBlock);
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
TouchBlockState*
|
||||
AsyncPanZoomController::CurrentTouchBlock()
|
||||
{
|
||||
AssertOnControllerThread();
|
||||
|
||||
MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
|
||||
return mTouchBlockQueue[0].get();
|
||||
}
|
||||
|
||||
bool
|
||||
AsyncPanZoomController::HasReadyTouchBlock()
|
||||
{
|
||||
return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
|
||||
}
|
||||
|
||||
AsyncPanZoomController::TouchBehaviorFlags
|
||||
@ -2552,13 +2644,6 @@ AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) {
|
||||
return AllowedTouchBehavior::UNKNOWN;
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
||||
mTouchBlockState.mAllowedTouchBehaviors.Clear();
|
||||
mTouchBlockState.mAllowedTouchBehaviors.AppendElements(aBehaviors);
|
||||
mTouchBlockState.mAllowedTouchBehaviorSet = true;
|
||||
CheckContentResponse();
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::SetState(PanZoomState aNewState) {
|
||||
|
||||
PanZoomState oldState;
|
||||
@ -2582,27 +2667,13 @@ void AsyncPanZoomController::SetState(PanZoomState aNewState) {
|
||||
}
|
||||
|
||||
bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) {
|
||||
return !(aState == NOTHING || aState == TOUCHING || aState == WAITING_CONTENT_RESPONSE);
|
||||
return !(aState == NOTHING || aState == TOUCHING);
|
||||
}
|
||||
|
||||
bool AsyncPanZoomController::IsPanningState(PanZoomState aState) {
|
||||
return (aState == PANNING || aState == PANNING_LOCKED_X || aState == PANNING_LOCKED_Y);
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::SetContentResponseTimer() {
|
||||
if (!mContentResponseTimeoutTask) {
|
||||
mContentResponseTimeoutTask =
|
||||
NewRunnableMethod(this, &AsyncPanZoomController::TimeoutContentResponse);
|
||||
|
||||
PostDelayedTask(mContentResponseTimeoutTask, gfxPrefs::APZContentResponseTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::TimeoutContentResponse() {
|
||||
mContentResponseTimeoutTask = nullptr;
|
||||
ContentReceivedTouch(false);
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) {
|
||||
APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom,
|
||||
aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale);
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "InputData.h"
|
||||
#include "Axis.h"
|
||||
@ -41,6 +42,7 @@ class PCompositorParent;
|
||||
struct ViewTransform;
|
||||
class AsyncPanZoomAnimation;
|
||||
class FlingAnimation;
|
||||
class TouchBlockState;
|
||||
|
||||
/**
|
||||
* Controller for all panning and zooming logic. Any time a user input is
|
||||
@ -111,6 +113,9 @@ public:
|
||||
* based on what type of input it is. For example, a PinchGestureEvent will
|
||||
* cause scaling. This should only be called externally to this class.
|
||||
* HandleInputEvent() should be used internally.
|
||||
* This function returns nsEventStatus_eIgnore for events that are ignored,
|
||||
* and nsEventStatus_eConsumeDoDefault for events that are queued for
|
||||
* processing pending a content response.
|
||||
*/
|
||||
nsEventStatus ReceiveInputEvent(const InputData& aEvent);
|
||||
|
||||
@ -121,14 +126,6 @@ public:
|
||||
*/
|
||||
void ZoomToRect(CSSRect aRect);
|
||||
|
||||
/**
|
||||
* If we have touch listeners, this should always be called when we know
|
||||
* definitively whether or not content has preventDefaulted any touch events
|
||||
* that have come in. If |aPreventDefault| is true, any touch events in the
|
||||
* queue will be discarded.
|
||||
*/
|
||||
void ContentReceivedTouch(bool aPreventDefault);
|
||||
|
||||
/**
|
||||
* Updates any zoom constraints contained in the <meta name="viewport"> tag.
|
||||
*/
|
||||
@ -303,15 +300,6 @@ public:
|
||||
*/
|
||||
TouchBehaviorFlags GetAllowedTouchBehavior(ScreenIntPoint& aPoint);
|
||||
|
||||
/**
|
||||
* Sets allowed touch behavior for current touch session.
|
||||
* This method is invoked by the APZCTreeManager which in its turn invoked by
|
||||
* the widget after performing touch-action values retrieving.
|
||||
* Must be called after receiving the TOUCH_START even that started the
|
||||
* touch session.
|
||||
*/
|
||||
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
||||
|
||||
/**
|
||||
* Returns whether this APZC is for an element marked with the 'scrollgrab'
|
||||
* attribute.
|
||||
@ -347,10 +335,6 @@ protected:
|
||||
|
||||
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
|
||||
ANIMATING_ZOOM, /* animated zoom to a new rect */
|
||||
WAITING_CONTENT_RESPONSE, /* a state halfway between NOTHING and TOUCHING - the user has
|
||||
put a finger down, but we don't yet know if a touch listener has
|
||||
prevented the default actions yet and the allowed touch behavior
|
||||
was not set yet. we still need to abort animations. */
|
||||
SNAP_BACK, /* snap-back animation to relieve overscroll */
|
||||
};
|
||||
|
||||
@ -487,7 +471,7 @@ protected:
|
||||
/**
|
||||
* Sets the panning state basing on the pan direction angle and current touch-action value.
|
||||
*/
|
||||
void HandlePanningWithTouchAction(double angle, TouchBehaviorFlags value);
|
||||
void HandlePanningWithTouchAction(double angle);
|
||||
|
||||
/**
|
||||
* Sets the panning state ignoring the touch action value.
|
||||
@ -543,24 +527,6 @@ protected:
|
||||
*/
|
||||
const FrameMetrics& GetFrameMetrics() const;
|
||||
|
||||
/**
|
||||
* Sets the timer for content response to a series of touch events, if it
|
||||
* hasn't been already. This is to prevent us from batching up touch events
|
||||
* indefinitely in the case that content doesn't respond with whether or not
|
||||
* it wants to preventDefault. When the timer is fired, the touch event queue
|
||||
* will be flushed.
|
||||
*/
|
||||
void SetContentResponseTimer();
|
||||
|
||||
/**
|
||||
* Timeout function for content response. This should be called on a timer
|
||||
* after we get our first touch event in a batch, under the condition that we
|
||||
* waiting for response from content. If a notification comes indicating whether or not
|
||||
* content preventDefaulted a series of touch events and touch behavior values are
|
||||
* set before the timeout, the timeout should be cancelled.
|
||||
*/
|
||||
void TimeoutContentResponse();
|
||||
|
||||
/**
|
||||
* Timeout function for mozbrowserasyncscroll event. Because we throttle
|
||||
* mozbrowserasyncscroll events in some conditions, this function ensures
|
||||
@ -570,62 +536,6 @@ protected:
|
||||
void FireAsyncScrollOnTimeout();
|
||||
|
||||
private:
|
||||
// State related to a single touch block. Does not persist across touch blocks.
|
||||
struct TouchBlockState {
|
||||
|
||||
TouchBlockState()
|
||||
: mAllowedTouchBehaviorSet(false),
|
||||
mPreventDefault(false),
|
||||
mPreventDefaultSet(false),
|
||||
mSingleTapOccurred(false)
|
||||
{}
|
||||
|
||||
// Values of allowed touch behavior for touch points of this touch block.
|
||||
// Since there are maybe a few current active touch points per time (multitouch case)
|
||||
// and each touch point should have its own value of allowed touch behavior- we're
|
||||
// keeping an array of allowed touch behavior values, not the single value.
|
||||
nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
|
||||
|
||||
// Specifies whether mAllowedTouchBehaviors is set for this touch events block.
|
||||
bool mAllowedTouchBehaviorSet;
|
||||
|
||||
// Flag used to specify that content prevented the default behavior of this
|
||||
// touch events block.
|
||||
bool mPreventDefault;
|
||||
|
||||
// Specifies whether mPreventDefault property is set for this touch events block.
|
||||
bool mPreventDefaultSet;
|
||||
|
||||
// Specifies whether a single tap event was generated during this touch block.
|
||||
bool mSingleTapOccurred;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns whether current touch behavior values allow pinch-zooming.
|
||||
*/
|
||||
bool TouchActionAllowPinchZoom();
|
||||
|
||||
/*
|
||||
* Returns whether current touch behavior values allow double-tap-zooming.
|
||||
*/
|
||||
bool TouchActionAllowDoubleTapZoom();
|
||||
|
||||
/*
|
||||
* Returns allowed touch behavior from the mAllowedTouchBehavior array.
|
||||
* In case apzc didn't receive touch behavior values within the timeout
|
||||
* it returns default value.
|
||||
*/
|
||||
TouchBehaviorFlags GetTouchBehavior(uint32_t touchIndex);
|
||||
|
||||
/**
|
||||
* To move from the WAITING_CONTENT_RESPONSE state to TOUCHING one we need two
|
||||
* conditions set: get content listeners response (whether they called preventDefault)
|
||||
* and get allowed touch behaviors.
|
||||
* This method checks both conditions and changes (or not changes) state
|
||||
* appropriately.
|
||||
*/
|
||||
void CheckContentResponse();
|
||||
|
||||
/**
|
||||
* Helper to set the current state. Holds the monitor before actually setting
|
||||
* it and fires content controller events based on state changes. Always set
|
||||
@ -734,10 +644,6 @@ private:
|
||||
// in a FIFO manner.
|
||||
FrameMetrics mLastDispatchedPaintMetrics;
|
||||
|
||||
nsTArray<MultiTouchInput> mTouchQueue;
|
||||
|
||||
CancelableTask* mContentResponseTimeoutTask;
|
||||
|
||||
AxisX mX;
|
||||
AxisY mY;
|
||||
|
||||
@ -771,19 +677,101 @@ private:
|
||||
// ensures the last mozbrowserasyncscroll event is always been fired.
|
||||
CancelableTask* mAsyncScrollTimeoutTask;
|
||||
|
||||
// Flag used to determine whether or not we should try to enter the
|
||||
// WAITING_LISTENERS state. This is used in the case that we are processing a
|
||||
// queued up event block. If set, this means that we are handling this queue
|
||||
// and we don't want to queue the events back up again.
|
||||
bool mHandlingTouchQueue;
|
||||
|
||||
// Stores information about the current touch block.
|
||||
TouchBlockState mTouchBlockState;
|
||||
|
||||
nsRefPtr<AsyncPanZoomAnimation> mAnimation;
|
||||
|
||||
friend class Axis;
|
||||
|
||||
/* ===================================================================
|
||||
* The functions and members in this section are used to manage
|
||||
* blocks of touch events and the state needed to deal with content
|
||||
* listeners.
|
||||
*/
|
||||
public:
|
||||
/**
|
||||
* This function is invoked by the APZCTreeManager which in turn is invoked
|
||||
* by the widget when web content decides whether or not it wants to
|
||||
* cancel a block of events. This automatically gets applied to the next
|
||||
* block of events that has not yet been responded to. This function MUST
|
||||
* be invoked exactly once for each touch block.
|
||||
*/
|
||||
void ContentReceivedTouch(bool aPreventDefault);
|
||||
|
||||
/**
|
||||
* Sets allowed touch behavior for current touch session.
|
||||
* This method is invoked by the APZCTreeManager which in its turn invoked by
|
||||
* the widget after performing touch-action values retrieving.
|
||||
* Must be called after receiving the TOUCH_START even that started the
|
||||
* touch session.
|
||||
*/
|
||||
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
||||
|
||||
private:
|
||||
void ScheduleContentResponseTimeout();
|
||||
void ContentResponseTimeout();
|
||||
/**
|
||||
* Processes any pending input blocks that are ready for processing. There
|
||||
* must be at least one input block in the queue when this function is called.
|
||||
*/
|
||||
void ProcessPendingInputBlocks();
|
||||
TouchBlockState* StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent);
|
||||
TouchBlockState* CurrentTouchBlock();
|
||||
bool HasReadyTouchBlock();
|
||||
|
||||
private:
|
||||
// The queue of touch blocks that have not yet been processed by this APZC.
|
||||
// This member must only be accessed on the controller/UI thread.
|
||||
nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
|
||||
|
||||
// This variable requires some explanation. Strap yourself in.
|
||||
//
|
||||
// For each block of events, we do two things: (1) send the events to gecko and expect
|
||||
// exactly one call to ContentReceivedTouch in return, and (2) kick off a timeout
|
||||
// that triggers in case we don't hear from web content in a timely fashion.
|
||||
// Since events are constantly coming in, we need to be able to handle more than one
|
||||
// block of input events sitting in the queue.
|
||||
//
|
||||
// There are ordering restrictions on events that we can take advantage of, and that
|
||||
// we need to abide by. Blocks of events in the queue will always be in the order that
|
||||
// the user generated them. Responses we get from content will be in the same order as
|
||||
// as the blocks of events in the queue. The timeout callbacks that have been posted
|
||||
// will also fire in the same order as the blocks of events in the queue.
|
||||
// HOWEVER, we may get multiple responses from content interleaved with multiple
|
||||
// timeout expirations, and that interleaving is not predictable.
|
||||
//
|
||||
// Therefore, we need to make sure that for each block of events, we process the queued
|
||||
// events exactly once, either when we get the response from content, or when the
|
||||
// timeout expires (whichever happens first). There is no way to associate the timeout
|
||||
// or response from content with a particular block of events other than via ordering.
|
||||
//
|
||||
// So, what we do to accomplish this is to track a "touch block balance", which is the
|
||||
// number of timeout expirations that have fired, minus the number of content responses
|
||||
// that have been received. (Think "balance" as in teeter-totter balance). This
|
||||
// value is:
|
||||
// - zero when we are in a state where the next content response we expect to receive
|
||||
// and the next timeout expiration we expect to fire both correspond to the next
|
||||
// unprocessed block of events in the queue.
|
||||
// - negative when we are in a state where we have received more content responses than
|
||||
// timeout expirations. This means that the next content repsonse we receive will
|
||||
// correspond to the first unprocessed block, but the next n timeout expirations need
|
||||
// to be ignored as they are for blocks we have already processed. (n is the absolute
|
||||
// value of the balance.)
|
||||
// - positive when we are in a state where we have received more timeout expirations
|
||||
// than content responses. This means that the next timeout expiration that we will
|
||||
// receive will correspond to the first unprocessed block, but the next n content
|
||||
// responses need to be ignored as they are for blocks we have already processed.
|
||||
// (n is the absolute value of the balance.)
|
||||
//
|
||||
// Note that each touch block internally carries flags that indicate whether or not it
|
||||
// has received a content response and/or timeout expiration. However, we cannot rely
|
||||
// on that alone to deliver these notifications to the right input block, because
|
||||
// once an input block has been processed, it can potentially be removed from the queue.
|
||||
// Therefore the information in that block is lost. An alternative approach would
|
||||
// be to keep around those blocks until they have received both the content response
|
||||
// and timeout expiration, but that involves a higher level of memory usage.
|
||||
//
|
||||
// This member must only be accessed on the controller/UI thread.
|
||||
int32_t mTouchBlockBalance;
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* The functions and members in this section are used to manage
|
||||
|
219
gfx/layers/apz/src/TouchBlockState.cpp
Normal file
219
gfx/layers/apz/src/TouchBlockState.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et 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 "TouchBlockState.h"
|
||||
#include "mozilla/layers/APZCTreeManager.h" // for AllowedTouchBehavior
|
||||
#include "gfxPrefs.h" // for gfxPrefs
|
||||
|
||||
#define TBS_LOG(...)
|
||||
// #define TBS_LOG(...) printf_stderr("TBS: " __VA_ARGS__)
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
/**
|
||||
* Default touch behavior (is used when no touch behavior is set).
|
||||
*/
|
||||
static const TouchBlockState::TouchBehaviorFlags kDefaultTouchBehavior =
|
||||
AllowedTouchBehavior::VERTICAL_PAN |
|
||||
AllowedTouchBehavior::HORIZONTAL_PAN |
|
||||
AllowedTouchBehavior::PINCH_ZOOM |
|
||||
AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
|
||||
|
||||
TouchBlockState::TouchBlockState()
|
||||
: mAllowedTouchBehaviorSet(false)
|
||||
, mPreventDefault(false)
|
||||
, mContentResponded(false)
|
||||
, mContentResponseTimerExpired(false)
|
||||
, mSingleTapOccurred(false)
|
||||
{
|
||||
TBS_LOG("Creating %p\n", this);
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::SetContentResponse(bool aPreventDefault)
|
||||
{
|
||||
if (mContentResponded) {
|
||||
return false;
|
||||
}
|
||||
TBS_LOG("%p got content response %d with timer expired %d\n",
|
||||
this, aPreventDefault, mContentResponseTimerExpired);
|
||||
if (!mContentResponseTimerExpired) {
|
||||
mPreventDefault = aPreventDefault;
|
||||
}
|
||||
mContentResponded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TimeoutContentResponse()
|
||||
{
|
||||
if (mContentResponseTimerExpired) {
|
||||
return false;
|
||||
}
|
||||
TBS_LOG("%p got content timer expired with response received %d\n",
|
||||
this, mContentResponded);
|
||||
if (!mContentResponded) {
|
||||
mPreventDefault = false;
|
||||
}
|
||||
mContentResponseTimerExpired = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::SetAllowedTouchBehaviors(const nsTArray<TouchBehaviorFlags>& aBehaviors)
|
||||
{
|
||||
if (mAllowedTouchBehaviorSet) {
|
||||
return false;
|
||||
}
|
||||
TBS_LOG("%p got allowed touch behaviours for %d points\n", this, aBehaviors.Length());
|
||||
mAllowedTouchBehaviors.AppendElements(aBehaviors);
|
||||
mAllowedTouchBehaviorSet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::CopyAllowedTouchBehaviorsFrom(const TouchBlockState& aOther)
|
||||
{
|
||||
TBS_LOG("%p copying allowed touch behaviours from %p\n", this, &aOther);
|
||||
MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet);
|
||||
return SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors);
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::IsReadyForHandling() const
|
||||
{
|
||||
// TODO: for long-tap blocks we probably don't need the touch behaviour?
|
||||
if (gfxPrefs::TouchActionEnabled() && !mAllowedTouchBehaviorSet) {
|
||||
return false;
|
||||
}
|
||||
if (!mContentResponded && !mContentResponseTimerExpired) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::IsDefaultPrevented() const
|
||||
{
|
||||
MOZ_ASSERT(mContentResponded || mContentResponseTimerExpired);
|
||||
return mPreventDefault;
|
||||
}
|
||||
|
||||
void
|
||||
TouchBlockState::SetSingleTapOccurred()
|
||||
{
|
||||
TBS_LOG("%p setting single-tap occurred\n", this);
|
||||
mSingleTapOccurred = true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::SingleTapOccurred() const
|
||||
{
|
||||
return mSingleTapOccurred;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::HasEvents() const
|
||||
{
|
||||
return !mEvents.IsEmpty();
|
||||
}
|
||||
|
||||
void
|
||||
TouchBlockState::AddEvent(const MultiTouchInput& aEvent)
|
||||
{
|
||||
TBS_LOG("%p adding event of type %d\n", this, aEvent.mType);
|
||||
mEvents.AppendElement(aEvent);
|
||||
}
|
||||
|
||||
void
|
||||
TouchBlockState::DropEvents()
|
||||
{
|
||||
TBS_LOG("%p dropping %d events\n", this, mEvents.Length());
|
||||
mEvents.Clear();
|
||||
}
|
||||
|
||||
MultiTouchInput
|
||||
TouchBlockState::RemoveFirstEvent()
|
||||
{
|
||||
MOZ_ASSERT(!mEvents.IsEmpty());
|
||||
TBS_LOG("%p returning first of %d events\n", this, mEvents.Length());
|
||||
MultiTouchInput event = mEvents[0];
|
||||
mEvents.RemoveElementAt(0);
|
||||
return event;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TouchActionAllowsPinchZoom() const
|
||||
{
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
// Pointer events specification requires that all touch points allow zoom.
|
||||
for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) {
|
||||
if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TouchActionAllowsDoubleTapZoom() const
|
||||
{
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) {
|
||||
if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TouchActionAllowsPanningX() const
|
||||
{
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
if (mAllowedTouchBehaviors.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
|
||||
return (flags & AllowedTouchBehavior::HORIZONTAL_PAN);
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TouchActionAllowsPanningY() const
|
||||
{
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
if (mAllowedTouchBehaviors.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
|
||||
return (flags & AllowedTouchBehavior::VERTICAL_PAN);
|
||||
}
|
||||
|
||||
bool
|
||||
TouchBlockState::TouchActionAllowsPanningXY() const
|
||||
{
|
||||
if (!gfxPrefs::TouchActionEnabled()) {
|
||||
return true;
|
||||
}
|
||||
if (mAllowedTouchBehaviors.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
|
||||
return (flags & AllowedTouchBehavior::HORIZONTAL_PAN)
|
||||
&& (flags & AllowedTouchBehavior::VERTICAL_PAN);
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
145
gfx/layers/apz/src/TouchBlockState.h
Normal file
145
gfx/layers/apz/src/TouchBlockState.h
Normal file
@ -0,0 +1,145 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et 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_layers_TouchBlockState_h
|
||||
#define mozilla_layers_TouchBlockState_h
|
||||
|
||||
#include "nsTArray.h" // for nsTArray
|
||||
#include "InputData.h" // for MultiTouchInput
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
/**
|
||||
* This class represents a single touch block. A touch block is
|
||||
* a set of touch events that can be cancelled by web content via
|
||||
* touch event listeners.
|
||||
*
|
||||
* Every touch-start event creates a new touch block. In this case, the
|
||||
* touch block consists of the touch-start, followed by all touch events
|
||||
* up to but not including the next touch-start (except in the case where
|
||||
* a long-tap happens, see below). Note that in particular we cannot know
|
||||
* when a touch block ends until the next one is started. Most touch
|
||||
* blocks are created by receipt of a touch-start event.
|
||||
*
|
||||
* Every long-tap event also creates a new touch block, since it can also
|
||||
* be consumed by web content. In this case, when the long-tap event is
|
||||
* dispatched to web content, a new touch block is started to hold the remaining
|
||||
* touch events, up to but not including the next touch start (or long-tap).
|
||||
*
|
||||
* Conceptually, each touch block can be cancelled by web content, and
|
||||
* this information is stored in the mPreventDefault flag. Because web
|
||||
* content runs on the Gecko main thread, we cannot always wait for web content's
|
||||
* response. Instead, there is a timeout that sets this flag in the case
|
||||
* where web content doesn't respond in time. The mContentResponded
|
||||
* and mContentResponseTimerExpired flags indicate which of these scenarios
|
||||
* occurred.
|
||||
*
|
||||
* Additionally, if touch-action is enabled, each touch block should
|
||||
* have a set of allowed touch behavior flags; one for each touch point.
|
||||
* This also requires running code on the Gecko main thread, and so may
|
||||
* be populated with some latency. The mAllowedTouchBehaviorSet and
|
||||
* mAllowedTouchBehaviors variables track this information.
|
||||
*/
|
||||
class TouchBlockState
|
||||
{
|
||||
public:
|
||||
typedef uint32_t TouchBehaviorFlags;
|
||||
|
||||
TouchBlockState();
|
||||
|
||||
/**
|
||||
* Record whether or not content cancelled this block of events.
|
||||
* @param aPreventDefault true iff the block is cancelled.
|
||||
* @return false if this block has already received a response from
|
||||
* web content, true if not.
|
||||
*/
|
||||
bool SetContentResponse(bool aPreventDefault);
|
||||
/**
|
||||
* Record that content didn't respond in time.
|
||||
* @return false if this block already timed out, true if not.
|
||||
*/
|
||||
bool TimeoutContentResponse();
|
||||
/**
|
||||
* Set the allowed touch behavior flags for this block.
|
||||
* @return false if this block already has these flags set, true if not.
|
||||
*/
|
||||
bool SetAllowedTouchBehaviors(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
||||
/**
|
||||
* Copy the allowed touch behavior flags from another block.
|
||||
* @return false if this block already has these flags set, true if not.
|
||||
*/
|
||||
bool CopyAllowedTouchBehaviorsFrom(const TouchBlockState& aOther);
|
||||
|
||||
/**
|
||||
* @return true iff this block has received all the information needed
|
||||
* to properly dispatch the events in the block.
|
||||
*/
|
||||
bool IsReadyForHandling() const;
|
||||
/**
|
||||
* @return true iff web content cancelled this block of events.
|
||||
*/
|
||||
bool IsDefaultPrevented() const;
|
||||
|
||||
/**
|
||||
* Set a flag that indicates that this touch block triggered a single tap event.
|
||||
*/
|
||||
void SetSingleTapOccurred();
|
||||
/**
|
||||
* @return true iff SetSingleTapOccurred was previously called on this block.
|
||||
*/
|
||||
bool SingleTapOccurred() const;
|
||||
|
||||
/**
|
||||
* @return true iff there are pending events in this touch block.
|
||||
*/
|
||||
bool HasEvents() const;
|
||||
/**
|
||||
* Add a new touch event to the queue of events in this input block.
|
||||
*/
|
||||
void AddEvent(const MultiTouchInput& aEvent);
|
||||
/**
|
||||
* Throw away all the events in this input block.
|
||||
*/
|
||||
void DropEvents();
|
||||
/**
|
||||
* @return the first event in the queue. The event is removed from the queue
|
||||
* before it is returned.
|
||||
*/
|
||||
MultiTouchInput RemoveFirstEvent();
|
||||
|
||||
/**
|
||||
* @return false iff touch-action is enabled and the allowed touch behaviors for
|
||||
* this touch block do not allow pinch-zooming.
|
||||
*/
|
||||
bool TouchActionAllowsPinchZoom() const;
|
||||
/**
|
||||
* @return false iff touch-action is enabled and the allowed touch behaviors for
|
||||
* this touch block do not allow double-tap zooming.
|
||||
*/
|
||||
bool TouchActionAllowsDoubleTapZoom() const;
|
||||
/**
|
||||
* @return false iff touch-action is enabled and the allowed touch behaviors for
|
||||
* the first touch point do not allow panning in the specified direction(s).
|
||||
*/
|
||||
bool TouchActionAllowsPanningX() const;
|
||||
bool TouchActionAllowsPanningY() const;
|
||||
bool TouchActionAllowsPanningXY() const;
|
||||
|
||||
private:
|
||||
nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
|
||||
bool mAllowedTouchBehaviorSet;
|
||||
bool mPreventDefault;
|
||||
bool mContentResponded;
|
||||
bool mContentResponseTimerExpired;
|
||||
bool mSingleTapOccurred;
|
||||
nsTArray<MultiTouchInput> mEvents;
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_layers_TouchBlockState_h
|
@ -235,6 +235,7 @@ UNIFIED_SOURCES += [
|
||||
'apz/src/Axis.cpp',
|
||||
'apz/src/GestureEventListener.cpp',
|
||||
'apz/src/TaskThrottler.cpp',
|
||||
'apz/src/TouchBlockState.cpp',
|
||||
'apz/testutil/APZTestData.cpp',
|
||||
'apz/util/ActiveElementManager.cpp',
|
||||
'apz/util/APZCCallbackHelper.cpp',
|
||||
|
@ -103,6 +103,8 @@ if CONFIG['INTEL_ARCHITECTURE'] and CONFIG['GNU_CC']:
|
||||
elif CONFIG['CPU_ARCH'] == 'arm' and CONFIG['GNU_CC'] and CONFIG['BUILD_ARM_NEON']:
|
||||
DEFINES['__ARM_HAVE_OPTIONAL_NEON_SUPPORT'] = 1
|
||||
DEFINES['USE_ANDROID_NDK_CPU_FEATURES'] = 0
|
||||
elif CONFIG['CLANG_CL']:
|
||||
SOURCES['trunk/src/opts/SkBitmapProcState_opts_SSSE3.cpp'].flags += ['-mssse3']
|
||||
|
||||
DEFINES['SKIA_IMPLEMENTATION'] = 1
|
||||
DEFINES['GR_IMPLEMENTATION'] = 1
|
||||
|
@ -911,6 +911,8 @@ if CONFIG['INTEL_ARCHITECTURE'] and CONFIG['GNU_CC']:
|
||||
elif CONFIG['CPU_ARCH'] == 'arm' and CONFIG['GNU_CC'] and CONFIG['BUILD_ARM_NEON']:
|
||||
DEFINES['__ARM_HAVE_OPTIONAL_NEON_SUPPORT'] = 1
|
||||
DEFINES['USE_ANDROID_NDK_CPU_FEATURES'] = 0
|
||||
elif CONFIG['CLANG_CL']:
|
||||
SOURCES['trunk/src/opts/SkBitmapProcState_opts_SSSE3.cpp'].flags += ['-mssse3']
|
||||
|
||||
DEFINES['SKIA_IMPLEMENTATION'] = 1
|
||||
DEFINES['GR_IMPLEMENTATION'] = 1
|
||||
|
@ -137,7 +137,12 @@ public:
|
||||
mFrameMetrics = metrics;
|
||||
}
|
||||
|
||||
FrameMetrics GetFrameMetrics() {
|
||||
FrameMetrics& GetFrameMetrics() {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
return mFrameMetrics;
|
||||
}
|
||||
|
||||
const FrameMetrics& GetFrameMetrics() const {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
return mFrameMetrics;
|
||||
}
|
||||
@ -200,11 +205,9 @@ protected:
|
||||
apzc->Destroy();
|
||||
}
|
||||
|
||||
void UseTouchListenerMetrics()
|
||||
void SetMayHaveTouchListeners()
|
||||
{
|
||||
FrameMetrics frameMetrics(TestFrameMetrics());
|
||||
frameMetrics.mMayHaveTouchListeners = true;
|
||||
apzc->SetFrameMetrics(frameMetrics);
|
||||
apzc->GetFrameMetrics().mMayHaveTouchListeners = true;
|
||||
}
|
||||
|
||||
void MakeApzcZoomable()
|
||||
@ -349,10 +352,10 @@ ApzcPanAndCheckStatus(AsyncPanZoomController* aApzc,
|
||||
ApzcPan(aApzc, aTreeManager, aTime, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses);
|
||||
|
||||
nsEventStatus touchStartStatus;
|
||||
if (hasTouchListeners) {
|
||||
if (hasTouchListeners || gfxPrefs::TouchActionEnabled()) {
|
||||
// APZC shouldn't consume the start event now, instead queueing it up
|
||||
// waiting for content's response.
|
||||
touchStartStatus = nsEventStatus_eIgnore;
|
||||
// waiting for content's response and/or allowed behavior.
|
||||
touchStartStatus = nsEventStatus_eConsumeDoDefault;
|
||||
} else {
|
||||
// APZC should go into the touching state and therefore consume the event.
|
||||
touchStartStatus = nsEventStatus_eConsumeNoDefault;
|
||||
@ -360,7 +363,10 @@ ApzcPanAndCheckStatus(AsyncPanZoomController* aApzc,
|
||||
EXPECT_EQ(touchStartStatus, statuses[0]);
|
||||
|
||||
nsEventStatus touchMoveStatus;
|
||||
if (expectIgnoredPan) {
|
||||
if (hasTouchListeners) {
|
||||
// APZC will queue up this event while waiting for content's response.
|
||||
touchMoveStatus = nsEventStatus_eConsumeDoDefault;
|
||||
} else if (expectIgnoredPan) {
|
||||
// APZC should ignore panning, be in TOUCHING state and therefore return eIgnore.
|
||||
// The same applies to all consequent touch move events.
|
||||
touchMoveStatus = nsEventStatus_eIgnore;
|
||||
@ -492,8 +498,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
void DoPinchTest(bool aShouldTriggerPinch,
|
||||
nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
|
||||
FrameMetrics GetPinchableFrameMetrics()
|
||||
{
|
||||
FrameMetrics fm;
|
||||
fm.mViewport = CSSRect(0, 0, 980, 480);
|
||||
@ -501,9 +506,14 @@ protected:
|
||||
fm.mScrollableRect = CSSRect(0, 0, 980, 1000);
|
||||
fm.SetScrollOffset(CSSPoint(300, 300));
|
||||
fm.SetZoom(CSSToScreenScale(2.0));
|
||||
apzc->SetFrameMetrics(fm);
|
||||
// the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
|
||||
return fm;
|
||||
}
|
||||
|
||||
void DoPinchTest(bool aShouldTriggerPinch,
|
||||
nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
|
||||
{
|
||||
apzc->SetFrameMetrics(GetPinchableFrameMetrics());
|
||||
MakeApzcZoomable();
|
||||
|
||||
if (aShouldTriggerPinch) {
|
||||
@ -521,7 +531,7 @@ protected:
|
||||
ApzcPinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
|
||||
}
|
||||
|
||||
fm = apzc->GetFrameMetrics();
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
|
||||
if (aShouldTriggerPinch) {
|
||||
// the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
|
||||
@ -604,6 +614,32 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAl
|
||||
DoPinchTest(false, &behaviors);
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
|
||||
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
|
||||
apzc->SetFrameMetrics(originalMetrics);
|
||||
|
||||
SetMayHaveTouchListeners();
|
||||
MakeApzcZoomable();
|
||||
|
||||
int touchInputId = 0;
|
||||
ApzcPinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId);
|
||||
|
||||
// Send the prevent-default notification for the touch block
|
||||
apzc->ContentReceivedTouch(true);
|
||||
|
||||
// Run all pending tasks (this should include at least the
|
||||
// prevent-default timer).
|
||||
EXPECT_LE(1, mcc->RunThroughDelayedTasks());
|
||||
|
||||
// verify the metrics didn't change (i.e. the pinch was ignored)
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
EXPECT_EQ(originalMetrics.GetZoom().scale, fm.GetZoom().scale);
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, Overzoom) {
|
||||
// the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
|
||||
FrameMetrics fm;
|
||||
@ -778,6 +814,35 @@ protected:
|
||||
EXPECT_EQ(ScreenPoint(), pointOut);
|
||||
EXPECT_EQ(ViewTransform(), viewTransformOut);
|
||||
}
|
||||
|
||||
void DoPanWithPreventDefaultTest()
|
||||
{
|
||||
SetMayHaveTouchListeners();
|
||||
|
||||
int time = 0;
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
ScreenPoint pointOut;
|
||||
ViewTransform viewTransformOut;
|
||||
|
||||
// Pan down
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
ApzcPanAndCheckStatus(apzc, tm, time, touchStart, touchEnd, true, true, &allowedTouchBehaviors);
|
||||
|
||||
// Send the signal that content has handled and preventDefaulted the touch
|
||||
// events. This flushes the event queue.
|
||||
apzc->ContentReceivedTouch(true);
|
||||
// Run all pending tasks (this should include at least the
|
||||
// prevent-default timer).
|
||||
EXPECT_LE(1, mcc->RunThroughDelayedTasks());
|
||||
|
||||
apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
|
||||
EXPECT_EQ(ScreenPoint(), pointOut);
|
||||
EXPECT_EQ(ViewTransform(), viewTransformOut);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(APZCPanningTester, Pan) {
|
||||
@ -810,28 +875,14 @@ TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
|
||||
DoPanTest(true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, PanWithPreventDefault) {
|
||||
TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
|
||||
UseTouchListenerMetrics();
|
||||
DoPanWithPreventDefaultTest();
|
||||
}
|
||||
|
||||
int time = 0;
|
||||
int touchStart = 50;
|
||||
int touchEnd = 10;
|
||||
ScreenPoint pointOut;
|
||||
ViewTransform viewTransformOut;
|
||||
|
||||
// Pan down
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
|
||||
ApzcPanAndCheckStatus(apzc, tm, time, touchStart, touchEnd, true, true, &allowedTouchBehaviors);
|
||||
|
||||
// Send the signal that content has handled and preventDefaulted the touch
|
||||
// events. This flushes the event queue.
|
||||
apzc->ContentReceivedTouch(true);
|
||||
|
||||
apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
|
||||
EXPECT_EQ(ScreenPoint(), pointOut);
|
||||
EXPECT_EQ(ViewTransform(), viewTransformOut);
|
||||
TEST_F(APZCPanningTester, PanWithPreventDefault) {
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
DoPanWithPreventDefaultTest();
|
||||
}
|
||||
|
||||
TEST_F(APZCBasicTester, Fling) {
|
||||
@ -1036,12 +1087,23 @@ protected:
|
||||
int time = 0;
|
||||
|
||||
nsEventStatus status = ApzcDown(apzc, 10, 10, time);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
// If touch-action is enabled, then the event is queued until the
|
||||
// allowed touch behavior is set.
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
} else {
|
||||
// Otherwise, it is processed immediately.
|
||||
EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
|
||||
}
|
||||
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
|
||||
}
|
||||
// Have content "respond" to the touchstart
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
|
||||
@ -1057,6 +1119,7 @@ protected:
|
||||
EXPECT_CALL(check, Call("postHandleLongTapUp"));
|
||||
}
|
||||
|
||||
// There is a longpress event scheduled on a timeout
|
||||
mcc->CheckHasDelayedTask();
|
||||
|
||||
// Manually invoke the longpress while the touch is currently down.
|
||||
@ -1066,24 +1129,23 @@ protected:
|
||||
|
||||
// Destroy pending MAX_TAP timeout task
|
||||
mcc->DestroyOldestTask();
|
||||
// There should be a TimeoutContentResponse task in the queue still
|
||||
// Clear the waiting-for-content timeout task, then send the signal that
|
||||
// content has handled this long tap. This takes the place of the
|
||||
// "contextmenu" event.
|
||||
|
||||
// Dispatching the longpress event starts a new touch block, which
|
||||
// needs a new content response and also has a pending timeout task
|
||||
// in the queue. Deal with those here. We do the content response first
|
||||
// with preventDefault=false, and then we run the timeout task which
|
||||
// "loses the race" and does nothing.
|
||||
apzc->ContentReceivedTouch(false);
|
||||
mcc->CheckHasDelayedTask();
|
||||
mcc->ClearDelayedTask();
|
||||
apzc->ContentReceivedTouch(true);
|
||||
mcc->RunDelayedTask();
|
||||
|
||||
time += 1000;
|
||||
|
||||
// Finally, simulate lifting the finger. Since the long-press wasn't
|
||||
// prevent-defaulted, we should get a long-tap-up event.
|
||||
check.Call("preHandleLongTapUp");
|
||||
status = ApzcUp(apzc, 10, 10, time);
|
||||
EXPECT_EQ(nsEventStatus_eIgnore, status);
|
||||
|
||||
// To get a LongTapUp event, we must kick APZC to flush its event queue. This
|
||||
// would normally happen if we had a (Tab|RenderFrame)(Parent|Child)
|
||||
// mechanism.
|
||||
check.Call("preHandleLongTapUp");
|
||||
apzc->ContentReceivedTouch(false);
|
||||
check.Call("postHandleLongTapUp");
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
@ -1101,12 +1163,23 @@ protected:
|
||||
|
||||
int time = 0;
|
||||
nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
// If touch-action is enabled, then the event is queued until the
|
||||
// allowed touch behavior is set.
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
|
||||
} else {
|
||||
// Otherwise, it is processed immediately.
|
||||
EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
|
||||
}
|
||||
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
|
||||
if (gfxPrefs::TouchActionEnabled()) {
|
||||
// SetAllowedTouchBehavior() must be called after sending touch-start.
|
||||
nsTArray<uint32_t> allowedTouchBehaviors;
|
||||
allowedTouchBehaviors.AppendElement(aBehavior);
|
||||
apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
|
||||
}
|
||||
// Have content "respond" to the touchstart
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
MockFunction<void(std::string checkPointName)> check;
|
||||
|
||||
@ -1127,11 +1200,15 @@ protected:
|
||||
|
||||
// Destroy pending MAX_TAP timeout task
|
||||
mcc->DestroyOldestTask();
|
||||
// Clear the waiting-for-content timeout task, then send the signal that
|
||||
// content has handled this long tap. This takes the place of the
|
||||
// "contextmenu" event.
|
||||
mcc->ClearDelayedTask();
|
||||
|
||||
// There should be a TimeoutContentResponse task in the queue still,
|
||||
// waiting for the response from the longtap event dispatched above.
|
||||
// Send the signal that content has handled the long-tap, and then run
|
||||
// the timeout task (it will be a no-op because the content "wins" the
|
||||
// race. This takes the place of the "contextmenu" event.
|
||||
apzc->ContentReceivedTouch(true);
|
||||
mcc->CheckHasDelayedTask();
|
||||
mcc->RunDelayedTask();
|
||||
|
||||
time += 1000;
|
||||
|
||||
@ -1144,11 +1221,6 @@ protected:
|
||||
status = ApzcUp(apzc, touchX, touchEndY, time);
|
||||
EXPECT_EQ(nsEventStatus_eIgnore, status);
|
||||
|
||||
// Flush the event queue. Once the "contextmenu" event is handled, any touch
|
||||
// events that come from the same series of start->n*move->end events should
|
||||
// be discarded, even if only the "contextmenu" event is preventDefaulted.
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
ScreenPoint pointOut;
|
||||
ViewTransform viewTransformOut;
|
||||
apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
|
||||
@ -1182,6 +1254,118 @@ TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
|
||||
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
|
||||
}
|
||||
|
||||
static void
|
||||
ApzcDoubleTap(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime,
|
||||
nsEventStatus (*aOutEventStatuses)[4] = nullptr)
|
||||
{
|
||||
nsEventStatus status = ApzcDown(aApzc, aX, aY, aTime);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[0] = status;
|
||||
}
|
||||
aTime += 10;
|
||||
status = ApzcUp(aApzc, aX, aY, aTime);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[1] = status;
|
||||
}
|
||||
aTime += 10;
|
||||
status = ApzcDown(aApzc, aX, aY, aTime);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[2] = status;
|
||||
}
|
||||
aTime += 10;
|
||||
status = ApzcUp(aApzc, aX, aY, aTime);
|
||||
if (aOutEventStatuses) {
|
||||
(*aOutEventStatuses)[3] = status;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ApzcDoubleTapAndCheckStatus(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime)
|
||||
{
|
||||
nsEventStatus statuses[4];
|
||||
ApzcDoubleTap(aApzc, aX, aY, aTime, &statuses);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
|
||||
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTap) {
|
||||
SetMayHaveTouchListeners();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
|
||||
int time = 0;
|
||||
ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedTouch(false);
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
|
||||
SetMayHaveTouchListeners();
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
int time = 0;
|
||||
ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedTouch(false);
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
|
||||
SetMayHaveTouchListeners();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
int time = 0;
|
||||
ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedTouch(true);
|
||||
apzc->ContentReceivedTouch(false);
|
||||
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
|
||||
SetMayHaveTouchListeners();
|
||||
MakeApzcZoomable();
|
||||
|
||||
EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
|
||||
|
||||
int time = 0;
|
||||
ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
|
||||
|
||||
// responses to the two touchstarts
|
||||
apzc->ContentReceivedTouch(true);
|
||||
apzc->ContentReceivedTouch(true);
|
||||
|
||||
while (mcc->RunThroughDelayedTasks());
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
// Layer tree for HitTesting1
|
||||
static already_AddRefed<mozilla::layers::Layer>
|
||||
CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
|
||||
|
@ -100,6 +100,9 @@ struct Zone;
|
||||
/* Default size for the generational nursery in bytes. */
|
||||
const uint32_t DefaultNurseryBytes = 16 * 1024 * 1024;
|
||||
|
||||
/* Default maximum heap size in bytes to pass to JS_NewRuntime(). */
|
||||
const uint32_t DefaultHeapMaxBytes = 32 * 1024 * 1024;
|
||||
|
||||
/*
|
||||
* We cannot expose the class hierarchy: the implementation is hidden. Instead
|
||||
* we provide cast functions with strong debug-mode assertions.
|
||||
|
@ -212,8 +212,10 @@ struct GCSizes
|
||||
struct StringInfo
|
||||
{
|
||||
#define FOR_EACH_SIZE(macro) \
|
||||
macro(Strings, IsLiveGCThing, gcHeap) \
|
||||
macro(Strings, NotLiveGCThing, mallocHeap) \
|
||||
macro(Strings, IsLiveGCThing, gcHeapLatin1) \
|
||||
macro(Strings, IsLiveGCThing, gcHeapTwoByte) \
|
||||
macro(Strings, NotLiveGCThing, mallocHeapLatin1) \
|
||||
macro(Strings, NotLiveGCThing, mallocHeapTwoByte)
|
||||
|
||||
StringInfo()
|
||||
: FOR_EACH_SIZE(ZERO_SIZE)
|
||||
|
@ -677,12 +677,107 @@ regexp_test_impl(JSContext *cx, CallArgs args)
|
||||
return status != RegExpRunStatus_Error;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
StringHasDotStar(HandleLinearString str, size_t index)
|
||||
{
|
||||
// Return whether the portion of the string at the specified index is '.*'
|
||||
return str->latin1OrTwoByteChar(index) == '.' && str->latin1OrTwoByteChar(index + 1) == '*';
|
||||
}
|
||||
|
||||
static bool
|
||||
TryFillRegExpTestCache(JSContext *cx, HandleObject regexp, RegExpTestCache &cache,
|
||||
MutableHandleObject result)
|
||||
{
|
||||
cache.purge();
|
||||
|
||||
// test() on global RegExps uses the lastIndex in a fashion that is
|
||||
// incompatible with the cache.
|
||||
if (regexp->as<RegExpObject>().global())
|
||||
return true;
|
||||
|
||||
RootedAtom source(cx, regexp->as<RegExpObject>().getSource());
|
||||
|
||||
// Try to strip a leading '.*' from the RegExp, but only if it is not
|
||||
// followed by a '?' (which will affect how the .* is parsed).
|
||||
if (source->length() >= 3 &&
|
||||
StringHasDotStar(source, 0) &&
|
||||
source->latin1OrTwoByteChar(2) != '?')
|
||||
{
|
||||
source = AtomizeSubstring(cx, source, 2, source->length() - 2);
|
||||
if (!source)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to strip a trailing '.*' from the RegExp, but only if it does not
|
||||
// have any other meta characters (to be sure we are not affecting how the
|
||||
// RegExp will be parsed).
|
||||
if (source->length() >= 3 &&
|
||||
StringHasDotStar(source, source->length() - 2) &&
|
||||
!StringHasRegExpMetaChars(source, 0, 2))
|
||||
{
|
||||
source = AtomizeSubstring(cx, source, 0, source->length() - 2);
|
||||
if (!source)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source == regexp->as<RegExpObject>().getSource()) {
|
||||
// We weren't able to remove a leading or trailing .*
|
||||
return true;
|
||||
}
|
||||
|
||||
RegExpObjectBuilder builder(cx);
|
||||
|
||||
result.set(builder.build(source, regexp->as<RegExpObject>().getFlags()));
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
cache.fill(®exp->as<RegExpObject>(), &result->as<RegExpObject>());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Separate interface for use by IonMonkey. */
|
||||
bool
|
||||
js::regexp_test_raw(JSContext *cx, HandleObject regexp, HandleString input, bool *result)
|
||||
{
|
||||
ScopedMatchPairs matches(&cx->tempLifoAlloc());
|
||||
RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, matches, UpdateRegExpStatics);
|
||||
|
||||
RegExpTestCache &cache = cx->runtime()->regExpTestCache;
|
||||
|
||||
RootedObject alternate(cx);
|
||||
if (regexp == cache.key ||
|
||||
(cache.key &&
|
||||
regexp->as<RegExpObject>().getSource() == cache.key->getSource() &&
|
||||
regexp->as<RegExpObject>().getFlags() == cache.key->getFlags()))
|
||||
{
|
||||
alternate = cache.value;
|
||||
} else {
|
||||
if (!TryFillRegExpTestCache(cx, regexp, cache, &alternate))
|
||||
return false;
|
||||
}
|
||||
|
||||
RegExpRunStatus status;
|
||||
if (alternate) {
|
||||
// The alternate RegExp is simpler and should execute faster than the
|
||||
// original one, so use it instead.
|
||||
status = ExecuteRegExp(cx, alternate, input, matches, DontUpdateRegExpStatics);
|
||||
|
||||
if (status == RegExpRunStatus_Success) {
|
||||
// Update the RegExpStatics to reflect the original RegExp we were
|
||||
// trying to execute, and not the alternate one.
|
||||
RegExpStatics *res = cx->global()->getRegExpStatics(cx);
|
||||
if (!res)
|
||||
return RegExpRunStatus_Error;
|
||||
|
||||
RegExpGuard shared(cx);
|
||||
if (!regexp->as<RegExpObject>().getShared(cx, &shared))
|
||||
return RegExpRunStatus_Error;
|
||||
|
||||
res->updateLazily(cx, &input->asLinear(), shared.re(), 0);
|
||||
}
|
||||
} else {
|
||||
status = ExecuteRegExp(cx, regexp, input, matches, UpdateRegExpStatics);
|
||||
}
|
||||
|
||||
*result = (status == RegExpRunStatus_Success);
|
||||
return status != RegExpRunStatus_Error;
|
||||
}
|
||||
|
@ -291,7 +291,9 @@ static const struct ParamPair {
|
||||
{"gcBytes", JSGC_BYTES},
|
||||
{"gcNumber", JSGC_NUMBER},
|
||||
{"sliceTimeBudget", JSGC_SLICE_TIME_BUDGET},
|
||||
{"markStackLimit", JSGC_MARK_STACK_LIMIT}
|
||||
{"markStackLimit", JSGC_MARK_STACK_LIMIT},
|
||||
{"minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT},
|
||||
{"maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT}
|
||||
};
|
||||
|
||||
// Keep this in sync with above params.
|
||||
|
@ -843,6 +843,7 @@ typedef ReadBarriered<UnownedBaseShape*> ReadBarrieredUnownedBaseShape;
|
||||
typedef ReadBarriered<jit::JitCode*> ReadBarrieredJitCode;
|
||||
typedef ReadBarriered<types::TypeObject*> ReadBarrieredTypeObject;
|
||||
typedef ReadBarriered<JSAtom*> ReadBarrieredAtom;
|
||||
typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol;
|
||||
|
||||
typedef ReadBarriered<Value> ReadBarrieredValue;
|
||||
|
||||
|
@ -480,6 +480,8 @@ class GCRuntime
|
||||
bool dynamicHeapGrowth;
|
||||
bool dynamicMarkSlice;
|
||||
uint64_t decommitThreshold;
|
||||
unsigned minEmptyChunkCount;
|
||||
unsigned maxEmptyChunkCount;
|
||||
|
||||
/* During shutdown, the GC needs to clean up every possible object. */
|
||||
bool cleanUpEverything;
|
||||
|
@ -729,12 +729,14 @@ MarkValueInternal(JSTracer *trc, Value *v)
|
||||
void *thing = v->toGCThing();
|
||||
trc->setTracingLocation((void *)v);
|
||||
MarkKind(trc, &thing, v->gcKind());
|
||||
if (v->isString())
|
||||
if (v->isString()) {
|
||||
v->setString((JSString *)thing);
|
||||
else if (v->isSymbol())
|
||||
v->setSymbol((JS::Symbol *)thing);
|
||||
else
|
||||
} else if (v->isObject()) {
|
||||
v->setObjectOrNull((JSObject *)thing);
|
||||
} else {
|
||||
JS_ASSERT(v->isSymbol());
|
||||
v->setSymbol((JS::Symbol *)thing);
|
||||
}
|
||||
} else {
|
||||
/* Unset realLocation manually if we do not call MarkInternal. */
|
||||
trc->unsetTracingLocation();
|
||||
@ -800,10 +802,15 @@ gc::IsValueMarked(Value *v)
|
||||
JSString *str = (JSString *)v->toGCThing();
|
||||
rv = IsMarked<JSString>(&str);
|
||||
v->setString(str);
|
||||
} else {
|
||||
} else if (v->isObject()) {
|
||||
JSObject *obj = (JSObject *)v->toGCThing();
|
||||
rv = IsMarked<JSObject>(&obj);
|
||||
v->setObject(*obj);
|
||||
} else {
|
||||
JS_ASSERT(v->isSymbol());
|
||||
JS::Symbol *sym = v->toSymbol();
|
||||
rv = IsMarked<JS::Symbol>(&sym);
|
||||
v->setSymbol(sym);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@ -817,10 +824,15 @@ gc::IsValueAboutToBeFinalized(Value *v)
|
||||
JSString *str = (JSString *)v->toGCThing();
|
||||
rv = IsAboutToBeFinalized<JSString>(&str);
|
||||
v->setString(str);
|
||||
} else {
|
||||
} else if (v->isObject()) {
|
||||
JSObject *obj = (JSObject *)v->toGCThing();
|
||||
rv = IsAboutToBeFinalized<JSObject>(&obj);
|
||||
v->setObject(*obj);
|
||||
} else {
|
||||
JS_ASSERT(v->isSymbol());
|
||||
JS::Symbol *sym = v->toSymbol();
|
||||
rv = IsAboutToBeFinalized<JS::Symbol>(&sym);
|
||||
v->setSymbol(sym);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user