Merge m-c to fx-team

This commit is contained in:
Wes Kocher 2014-07-16 17:46:33 -07:00
commit d778407bb2
281 changed files with 7200 additions and 5472 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1024707 - It seems like changing 3 uuids is just too much!
Bug 1038799 - And be wary of your ccache too.

View File

@ -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);

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -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"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "8cd6c73ef83257a569d148e246108b2c161127bb",
"revision": "6739781fb8d0f3ae8bff65d1093e74d9f21ed6e5",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="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"/>

View File

@ -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/");

View File

@ -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();
},
};

View File

@ -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();

View File

@ -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"

View File

@ -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);

View File

@ -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)

View File

@ -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"/>

View File

@ -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,

View File

@ -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);
},

View File

@ -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);

View File

@ -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();
},
});

View File

@ -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

View File

@ -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
-----

View File

@ -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)) {

View File

@ -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.

View File

@ -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() {

View File

@ -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});
});

View File

@ -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();

View File

@ -102,7 +102,10 @@ describe("loop.shared.router", function() {
});
conversation = new loop.shared.models.ConversationModel({
loopToken: "fakeToken"
}, {sdk: {}});
}, {
sdk: {},
pendingCallTimeout: 1000
});
});
describe("#constructor", function() {

View File

@ -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
});
});

View File

@ -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

View File

@ -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 += [

View File

@ -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 */

View File

@ -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 {

View File

@ -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 */

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<body>
</body>
</html>

View File

@ -0,0 +1,2 @@
HTTP 301 Moved Permanently
Location: http://mochi.test:8888/tests/content/html/content/test/file_imports_redirected.html

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<script> var redirected = true; </script>
</body>
</html>

View File

@ -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]

View 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>

View 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>

View File

@ -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

View File

@ -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;

View File

@ -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()) {

View File

@ -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() {}

View File

@ -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();

View File

@ -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.

View File

@ -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;
}
};

View File

@ -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;
}

View File

@ -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();

View File

@ -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(

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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()|.
*/

View File

@ -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]

View File

@ -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;
}
});
});

View File

@ -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.
*/

View File

@ -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;
}

View File

@ -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;

View File

@ -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 ||

View File

@ -486,6 +486,7 @@ protected:
nsString mID;
bool mHasFacingMode;
dom::VideoFacingModeEnum mFacingMode;
dom::MediaSourceEnum mMediaSource;
nsRefPtr<MediaEngineSource> mSource;
};

View File

@ -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);
}
},

View File

@ -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)]

View File

@ -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;

View 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__

View 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

View 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__

View 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);
}

View File

@ -0,0 +1,32 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#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__

View File

@ -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']

View File

@ -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;

View File

@ -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;

View File

@ -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 ?

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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

View 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

View 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

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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.

View File

@ -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)

View File

@ -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(&regexp->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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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