Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-02-16 16:14:51 +01:00
commit e2399947f4
120 changed files with 2872 additions and 278 deletions

View File

@ -1114,8 +1114,10 @@ pref("dom.requestSync.enabled", true);
pref("gfx.vsync.hw-vsync.enabled", true);
pref("gfx.vsync.compositor", true);
pref("gfx.touch.resample", true);
pref("gfx.vsync.refreshdriver", true);
#else
pref("gfx.vsync.hw-vsync.enabled", false);
pref("gfx.vsync.compositor", false);
pref("gfx.touch.resample", false);
pref("gfx.vsync.refreshdriver", false);
#endif

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

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="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

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="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "f0b93e0668ef9565bd6f050b15b4f794d59feb65",
"git_revision": "ae02fbdeae77b2002cebe33c61aedeee4b9439fd",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "d170342f8e1837435749392fc2bded3b9fee01d4",
"revision": "62d026a98ea42f2b93de000e8d0d4f1254f86730",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="7f2ee9f4cb926684883fc2a2e407045fd9db2199">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423263875000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423697587000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
@ -220,6 +220,26 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i858" id="fftoolbar2014@etech.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
<pref>browser.startup.homepage</pref>
<pref>browser.search.defaultenginename</pref>
</prefs>
</emItem>
<emItem blockID="i854" id="/^(7tG@zEb\.net|ru@gfK0J\.edu)$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i850" id="P2@D.edu">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i554" id="lightningnewtab@gmail.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
@ -268,6 +288,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i856" id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i482" id="brasilescapeeight@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -452,6 +478,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i848" id="bcVX5@nQm9l.org">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i431" id="chinaescapeone@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -1544,6 +1576,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i852" id="6lIy@T.edu">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i505" id="extacylife@a.com">
<versionRange minVersion="0" maxVersion="*" severity="3">

View File

@ -415,3 +415,10 @@
label="&inspectContextMenu.label;"
accesskey="&inspectContextMenu.accesskey;"
oncommand="gContextMenu.inspectNode();"/>
<menuseparator id="context-media-eme-separator" hidden="true"/>
<menuitem id="context-media-eme-learnmore"
class="menuitem-iconic"
hidden="true"
label="&emeLearnMoreContextMenu.label;"
accesskey="&emeLearnMoreContextMenu.accesskey;"
onclick="gContextMenu.drmLearnMore(event);"/>

View File

@ -478,6 +478,8 @@ nsContextMenu.prototype = {
var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
this.showItem("context-media-eme-learnmore", this.onDRMMedia);
this.showItem("context-media-eme-separator", this.onDRMMedia);
// Disable them when there isn't a valid media source loaded.
if (onMedia) {
@ -499,7 +501,7 @@ nsContextMenu.prototype = {
this.setItemAttr("context-media-showcontrols", "disabled", hasError);
this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
if (this.onVideo) {
let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
this.setItemAttr("context-video-fullscreen", "disabled", hasError);
this.setItemAttr("context-video-showstats", "disabled", hasError);
@ -562,6 +564,7 @@ nsContextMenu.prototype = {
this.onCanvas = false;
this.onVideo = false;
this.onAudio = false;
this.onDRMMedia = false;
this.onTextInput = false;
this.onNumeric = false;
this.onKeywordField = false;
@ -640,6 +643,9 @@ nsContextMenu.prototype = {
if (this.isMediaURLReusable(mediaURL)) {
this.mediaURL = mediaURL;
}
if (this.target.isEncrypted) {
this.onDRMMedia = true;
}
// Firefox always creates a HTMLVideoElement when loading an ogg file
// directly. If the media is actually audio, be smarter and provide a
// context menu with audio operations.
@ -656,6 +662,9 @@ nsContextMenu.prototype = {
if (this.isMediaURLReusable(mediaURL)) {
this.mediaURL = mediaURL;
}
if (this.target.isEncrypted) {
this.onDRMMedia = true;
}
}
else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
@ -1698,6 +1707,17 @@ nsContextMenu.prototype = {
clipboard.copyString(this.mediaURL, document);
},
drmLearnMore: function(aEvent) {
let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
let dest = whereToOpenLink(aEvent);
// Don't ever want this to open in the same tab as it'll unload the
// DRM'd video, which is going to be a bad idea in most cases.
if (dest == "current") {
dest = "tab";
}
openUILinkIn(drmInfoURL, dest);
},
get imageURL() {
if (this.onImage)
return this.mediaURL;

View File

@ -61,6 +61,7 @@ support-files =
head.js
healthreport_testRemoteCommands.html
moz.png
navigating_window_with_download.html
offlineQuotaNotification.cacheManifest
offlineQuotaNotification.html
page_style_sample.html
@ -82,6 +83,8 @@ support-files =
test_process_flags_chrome.html
test_wyciwyg_copying.html
title_test.svg
unknownContentType_file.pif
unknownContentType_file.pif^headers^
video.ogg
web_video.html
web_video1.ogv
@ -387,6 +390,8 @@ skip-if = buildapp == 'mulet'
skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_private_link_perwindowpb.js]
skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_link_when_window_navigates.js]
skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_video.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
[browser_save_video_frame.js]

View File

@ -0,0 +1,173 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
this);
function createTemporarySaveDirectory() {
var saveDir = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("TmpD", Ci.nsIFile);
saveDir.append("testsavedir");
if (!saveDir.exists()) {
info("create testsavedir!");
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
}
info("return from createTempSaveDir: " + saveDir.path);
return saveDir;
}
function triggerSave(aWindow, aCallback) {
info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
var fileName;
let testBrowser = aWindow.gBrowser.selectedBrowser;
let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
windowObserver.setCallback(onUCTDialog);
testBrowser.loadURI(testURI);
// Create the folder the link will be saved into.
var destDir = createTemporarySaveDirectory();
var destFile = destDir.clone();
MockFilePicker.displayDirectory = destDir;
MockFilePicker.showCallback = function(fp) {
info("showCallback");
fileName = fp.defaultString;
info("fileName: " + fileName);
destFile.append (fileName);
MockFilePicker.returnFiles = [destFile];
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
info("done showCallback");
};
mockTransferCallback = function(downloadSuccess) {
info("mockTransferCallback");
onTransferComplete(aWindow, downloadSuccess, destDir);
destDir.remove(true);
ok(!destDir.exists(), "Destination dir should be removed");
ok(!destFile.exists(), "Destination file should be removed");
mockTransferCallback = null;
info("done mockTransferCallback");
}
function onUCTDialog(dialog) {
function doLoad() {
content.document.querySelector('iframe').remove();
}
testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
executeSoon(continueDownloading);
}
function continueDownloading() {
let windows = Services.wm.getEnumerator("");
while (windows.hasMoreElements()) {
let win = windows.getNext();
if (win.location && win.location.href == UCT_URI) {
win.document.documentElement._fireButtonEvent("accept");
win.close();
return;
}
}
ok(false, "No Unknown Content Type dialog yet?");
}
function onTransferComplete(aWindow, downloadSuccess, destDir) {
ok(downloadSuccess, "Link should have been downloaded successfully");
aWindow.close();
executeSoon(aCallback);
}
}
let windowObserver = {
setCallback: function(aCallback) {
if (this._callback) {
ok(false, "Should only be dealing with one callback at a time.");
}
this._callback = aCallback;
},
observe: function(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened") {
return;
}
let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
win.addEventListener("load", function onLoad(event) {
win.removeEventListener("load", onLoad, false);
if (win.location == UCT_URI) {
SimpleTest.executeSoon(function() {
if (windowObserver._callback) {
windowObserver._callback(win);
delete windowObserver._callback;
} else {
ok(false, "Unexpected UCT dialog!");
}
});
}
}, false);
}
};
Services.ww.registerNotification(windowObserver);
function test() {
waitForExplicitFinish();
function testOnWindow(options, callback) {
info("testOnWindow(" + options + ")");
var win = OpenBrowserWindow(options);
info("got " + win);
whenDelayedStartupFinished(win, () => callback(win));
}
function whenDelayedStartupFinished(aWindow, aCallback) {
info("whenDelayedStartupFinished");
Services.obs.addObserver(function observer(aSubject, aTopic) {
info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
if (aWindow == aSubject) {
Services.obs.removeObserver(observer, aTopic);
executeSoon(aCallback);
info("whenDelayedStartupFinished found our window");
}
}, "browser-delayed-startup-finished", false);
}
mockTransferRegisterer.register();
registerCleanupFunction(function () {
info("Running the cleanup code");
mockTransferRegisterer.unregister();
MockFilePicker.cleanup();
Services.ww.unregisterNotification(windowObserver);
Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
info("Finished running the cleanup code");
});
Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
testOnWindow(undefined, function(win) {
let windowGonePromise = promiseWindowWillBeClosed(win);
Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
triggerSave(win, function() {
windowGonePromise.then(function() {
Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
testOnWindow(undefined, function(win) {
triggerSave(win, finish);
});
});
});
});
}

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head><title>This window will navigate while you're downloading something</title></head>
<body>
<iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
</body>
</html>

View File

@ -0,0 +1 @@
Dummy content for unknownContentType_dialog_layout_data.pif

View File

@ -0,0 +1 @@
Content-Type: application/octet-stream

View File

@ -295,30 +295,54 @@ loop.standaloneRoomViews = (function(mozL10n) {
* @param {Object} ratio Aspect ratio of the local camera stream
*/
updateLocalCameraPosition: function(ratio) {
// The local stream is a quarter of the remote stream.
var LOCAL_STREAM_SIZE = 0.25;
// The local stream overlaps the remote stream by a quarter of the local stream.
var LOCAL_STREAM_OVERLAP = 0.25;
// The minimum size of video height/width allowed by the sdk css.
var SDK_MIN_SIZE = 48;
var node = this._getElement(".local");
var parent = node.offsetParent || this._getElement(".media");
// The local camera view should be a sixth of the size of its offset parent
// and positioned to overlap with the remote stream at a quarter of its width.
var parentWidth = parent.offsetWidth;
var targetWidth = parentWidth / 6;
var targetWidth;
node.style.right = "auto";
if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
// For reduced screen widths, we just go for a fixed size and no overlap.
targetWidth = 180;
node.style.width = (targetWidth * ratio.width) + "px";
node.style.height = (targetWidth * ratio.height) + "px";
node.style.left = "auto";
} else {
// The local camera view should be a quarter of the size of the remote stream
// and positioned to overlap with the remote stream at a quarter of its width.
// Now position the local camera view correctly with respect to the remote
// video stream.
var remoteVideoDimensions = this.getRemoteVideoDimensions();
targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
var realWidth = targetWidth * ratio.width;
var realHeight = targetWidth * ratio.height;
// If we've hit the min size limits, then limit at the minimum.
if (realWidth < SDK_MIN_SIZE) {
realWidth = SDK_MIN_SIZE;
realHeight = realWidth / ratio.width * ratio.height;
}
if (realHeight < SDK_MIN_SIZE) {
realHeight = SDK_MIN_SIZE;
realWidth = realHeight / ratio.height * ratio.width;
}
var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
// The horizontal offset of the stream, and the width of the resulting
// pillarbox, is determined by the height exponent of the aspect ratio.
// Therefore we multiply the width of the local camera view by the height
// ratio.
node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
node.style.width = realWidth + "px";
node.style.height = realHeight + "px";
}
node.style.width = (targetWidth * ratio.width) + "px";
node.style.height = (targetWidth * ratio.height) + "px";
},
/**

View File

@ -295,30 +295,54 @@ loop.standaloneRoomViews = (function(mozL10n) {
* @param {Object} ratio Aspect ratio of the local camera stream
*/
updateLocalCameraPosition: function(ratio) {
// The local stream is a quarter of the remote stream.
var LOCAL_STREAM_SIZE = 0.25;
// The local stream overlaps the remote stream by a quarter of the local stream.
var LOCAL_STREAM_OVERLAP = 0.25;
// The minimum size of video height/width allowed by the sdk css.
var SDK_MIN_SIZE = 48;
var node = this._getElement(".local");
var parent = node.offsetParent || this._getElement(".media");
// The local camera view should be a sixth of the size of its offset parent
// and positioned to overlap with the remote stream at a quarter of its width.
var parentWidth = parent.offsetWidth;
var targetWidth = parentWidth / 6;
var targetWidth;
node.style.right = "auto";
if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
// For reduced screen widths, we just go for a fixed size and no overlap.
targetWidth = 180;
node.style.width = (targetWidth * ratio.width) + "px";
node.style.height = (targetWidth * ratio.height) + "px";
node.style.left = "auto";
} else {
// The local camera view should be a quarter of the size of the remote stream
// and positioned to overlap with the remote stream at a quarter of its width.
// Now position the local camera view correctly with respect to the remote
// video stream.
var remoteVideoDimensions = this.getRemoteVideoDimensions();
targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
var realWidth = targetWidth * ratio.width;
var realHeight = targetWidth * ratio.height;
// If we've hit the min size limits, then limit at the minimum.
if (realWidth < SDK_MIN_SIZE) {
realWidth = SDK_MIN_SIZE;
realHeight = realWidth / ratio.width * ratio.height;
}
if (realHeight < SDK_MIN_SIZE) {
realHeight = SDK_MIN_SIZE;
realWidth = realHeight / ratio.height * ratio.width;
}
var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
// The horizontal offset of the stream, and the width of the resulting
// pillarbox, is determined by the height exponent of the aspect ratio.
// Therefore we multiply the width of the local camera view by the height
// ratio.
node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
node.style.width = realWidth + "px";
node.style.height = realHeight + "px";
}
node.style.width = (targetWidth * ratio.width) + "px";
node.style.height = (targetWidth * ratio.height) + "px";
},
/**

View File

@ -143,6 +143,108 @@ describe("loop.standaloneRoomViews", function() {
});
});
describe("Local Stream Size Position", function() {
var view, localElement;
beforeEach(function() {
sandbox.stub(window, "matchMedia").returns({
matches: false
});
view = mountTestComponent();
localElement = view._getElement(".local");
});
it("should be a quarter of the width of the main stream", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 640,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 1,
height: 0.75
});
expect(localElement.style.width).eql("160px");
expect(localElement.style.height).eql("120px");
});
it("should be a quarter of the width reduced for aspect ratio", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 640,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 0.75,
height: 1
});
expect(localElement.style.width).eql("120px");
expect(localElement.style.height).eql("160px");
});
it("should ensure the height is a minimum of 48px", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 180,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 1,
height: 0.75
});
expect(localElement.style.width).eql("64px");
expect(localElement.style.height).eql("48px");
});
it("should ensure the width is a minimum of 48px", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 180,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 0.75,
height: 1
});
expect(localElement.style.width).eql("48px");
expect(localElement.style.height).eql("64px");
});
it("should position the stream to overlap the main stream by a quarter", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 640,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 1,
height: 0.75
});
expect(localElement.style.width).eql("160px");
expect(localElement.style.left).eql("600px");
});
it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
sandbox.stub(view, "getRemoteVideoDimensions").returns({
streamWidth: 640,
offsetX: 0
});
view.updateLocalCameraPosition({
width: 0.75,
height: 1
});
expect(localElement.style.width).eql("120px");
expect(localElement.style.left).eql("610px");
});
});
describe("#render", function() {
var view;

View File

@ -66,7 +66,9 @@
</tree>
<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="button-container">
<button class="primary" label="&welcomeback2.restoreButton;"
<button class="primary"
id="errorTryAgain"
label="&welcomeback2.restoreButton;"
accesskey="&welcomeback2.restoreButton.access;"
oncommand="restoreSession();"/>
</hbox>

View File

@ -138,6 +138,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
"resource://gre/modules/AddonWatcher.jsm");
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
@ -564,6 +567,76 @@ BrowserGlue.prototype = {
this._distributionCustomizer.applyPrefDefaults();
},
_notifySlowAddon: function BG_notifySlowAddon(addonId) {
let addonCallback = function(addon) {
if (!addon) {
Cu.reportError("couldn't look up addon: " + addonId);
return;
}
let win = RecentWindow.getMostRecentBrowserWindow();
if (!win) {
return;
}
let brandBundle = win.document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
let notificationBox = win.document.getElementById("global-notificationbox");
let notificationId = 'addon-slow:' + addonId;
let notification = notificationBox.getNotificationWithValue(notificationId);
if(notification) {
notification.label = message;
} else {
let buttons = [
{
label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
callback: function() {
addon.userDisabled = true;
if (addon.pendingOperations != addon.PENDING_NONE) {
let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
let restartButton = [
{
label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
callback: function() {
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
.getService(Ci.nsIAppStartup);
appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
}
}
];
const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
priority, restartButton);
}
}
},
{
label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
callback: function() {
AddonWatcher.ignoreAddonForSession(addonId);
}
},
{
label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
callback: function() {
AddonWatcher.ignoreAddonPermanently(addonId);
}
},
];
const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
notificationBox.appendNotification(message, notificationId, "",
priority, buttons);
}
};
AddonManager.getAddonByID(addonId, addonCallback);
},
// runs on startup, before the first command line handler is invoked
// (i.e. before the first window is opened)
_finalUIStartup: function BG__finalUIStartup() {
@ -612,6 +685,8 @@ BrowserGlue.prototype = {
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
AddonWatcher.init(this._notifySlowAddon);
},
_checkForOldBuildUpdates: function () {

View File

@ -31,7 +31,7 @@
<key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
</keyset>
<vbox flex="1" class="contentPane">
<vbox flex="1" class="contentPane largeDialogContainer">
<hbox align="center">
<label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
<textbox type="search" id="filter" flex="1"

View File

@ -22,6 +22,7 @@
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<prefpane id="FontsDialogPane"
class="largeDialogContainer"
helpTopic="prefs-fonts-and-colors">
<preferences id="fontPreferences">

View File

@ -37,6 +37,12 @@ let gSubDialog = {
// Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
// otherwise there is a flicker of the stylesheet applying.
this._frame.addEventListener("load", this._onLoad.bind(this));
chromeBrowser.addEventListener("unload", function(aEvent) {
if (aEvent.target.location.href != "about:blank") {
this.close();
}
}.bind(this), true);
},
uninit: function() {
@ -92,6 +98,8 @@ let gSubDialog = {
// Clear the sizing attributes
this._box.removeAttribute("width");
this._box.removeAttribute("height");
this._box.style.removeProperty("min-height");
this._box.style.removeProperty("min-width");
setTimeout(() => {
// Unload the dialog after the event listeners run so that the load of about:blank isn't
@ -144,12 +152,42 @@ let gSubDialog = {
// Do this on load to wait for the CSS to load and apply before calculating the size.
let docEl = this._frame.contentDocument.documentElement;
// padding-bottom doesn't seem to be included in the scrollHeight of the document element in XUL
// so add it ourselves.
let paddingBottom = parseFloat(this._frame.contentWindow.getComputedStyle(docEl).paddingBottom);
let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
let groupBoxTitleHeight = groupBoxTitle.clientHeight +
parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
this._frame.style.width = docEl.style.width || docEl.scrollWidth + "px";
this._frame.style.height = docEl.style.height || (docEl.scrollHeight + paddingBottom) + "px";
let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
let frameWidth = docEl.style.width || docEl.scrollWidth + "px";
let frameHeight = docEl.style.height || docEl.scrollHeight + "px";
let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
let frameRect = this._frame.getBoundingClientRect();
let boxRect = this._box.getBoundingClientRect();
let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
// Now check if the frame height we calculated is possible at this window size,
// accounting for titlebar, padding/border and some spacing.
let maxHeight = window.innerHeight - frameSizeDifference - 30;
if (frameHeight > maxHeight) {
// If not, we should probably let the dialog scroll:
frameHeight = maxHeight;
let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
for (let container of containers) {
container.classList.add("doScroll");
}
}
this._frame.style.width = frameWidth;
this._frame.style.height = frameHeight;
this._box.style.minHeight = "calc(" +
(boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
"px + " + frameHeight + ")";
this._box.style.minWidth = "calc(" +
(boxHorizontalBorder + boxHorizontalPadding) +
"px + " + frameWidth + ")";
this._overlay.style.visibility = "visible";
this._frame.focus();

View File

@ -76,7 +76,7 @@ let gTests = [{
dialog.document.documentElement.cancelDialog();
let closingEvent = yield closingPromise;
ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'accept'");
ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'cancel'");
yield deferredClose.promise;
ise(rv.acceptCount, 0, "return value should NOT have been updated");
@ -118,6 +118,26 @@ let gTests = [{
ise(rv.acceptCount, 0, "return value should NOT have been updated");
},
},
{
desc: "Check that 'back' navigation will close the dialog",
run: function* () {
let rv = { acceptCount: 0 };
let deferredClose = Promise.defer();
let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
(aEvent) => dialogClosingCallback(deferredClose, aEvent));
let dialog = yield dialogPromise;
// XXX Without the call to promiseDialogClosing the test causes
// intermittent failures in browser_change_app_handler.js.
let unusedClosingPromise = promiseDialogClosing(dialog);
info("cancelling the dialog");
content.gSubDialog._frame.goBack();
yield deferredClose.promise;
ise(rv.acceptCount, 0, "return value should NOT have been updated");
},
},
{
desc: "Hitting escape in the dialog",
run: function* () {

View File

@ -22,6 +22,7 @@
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<prefpane id="LanguagesDialogPane"
class="largeDialogContainer"
onpaneload="gLanguagesDialog.init();"
helpTopic="prefs-languages">

View File

@ -29,7 +29,7 @@
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane" flex="1">
<vbox class="contentPane largeDialogContainer" flex="1">
<description id="permissionsText" control="url"/>
<separator class="thin"/>
<label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>

View File

@ -82,8 +82,8 @@
<caption label="&dataSection.label;"/>
<grid flex="1">
<columns>
<column dialogWidth="&column.width2;"
subdialogWidth="&inContentColumn.width;"/>
<column dialogWidth="&sanitizePrefs2.column.width;"
subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
<column flex="1"/>
</columns>
<rows>

View File

@ -28,45 +28,47 @@
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
<separator class="thin"/>
<tree id="languagesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
onselect="gTranslationExceptions.onLanguageSelected();">
<treecols>
<treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<button id="removeLanguage" disabled="true"
accesskey="&removeLanguage.accesskey;"
icon="remove" label="&removeLanguage.label;"
oncommand="gTranslationExceptions.onLanguageDeleted();"/>
<button id="removeAllLanguages"
icon="clear" label="&removeAllLanguages.label;"
accesskey="&removeAllLanguages.accesskey;"
oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
<spacer flex="1"/>
<vbox class="largeDialogContainer">
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
<separator class="thin"/>
<tree id="languagesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
onselect="gTranslationExceptions.onLanguageSelected();">
<treecols>
<treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<button id="removeLanguage" disabled="true"
accesskey="&removeLanguage.accesskey;"
icon="remove" label="&removeLanguage.label;"
oncommand="gTranslationExceptions.onLanguageDeleted();"/>
<button id="removeAllLanguages"
icon="clear" label="&removeAllLanguages.label;"
accesskey="&removeAllLanguages.accesskey;"
oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
<spacer flex="1"/>
</hbox>
</hbox>
</hbox>
<separator/>
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
<separator class="thin"/>
<tree id="sitesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
onselect="gTranslationExceptions.onSiteSelected();">
<treecols>
<treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
<separator/>
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
<separator class="thin"/>
<tree id="sitesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
onselect="gTranslationExceptions.onSiteSelected();">
<treecols>
<treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
</vbox>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">

View File

@ -38,9 +38,9 @@ add_task(function* test_remove() {
is(hiddenOneOffs.length, 1,
"hiddenOneOffs has the correct engine count post removal.");
is(hiddenOneOffs.includes("FooDupe"), false,
is(hiddenOneOffs.some(x => x == "FooDupe"), false,
"Removed Engine is not in hiddenOneOffs after removal");
is(hiddenOneOffs.includes("Foo"), true,
is(hiddenOneOffs.some(x => x == "Foo"), true,
"Current hidden engine is not affected by removal.");
info("Removing testEngine.xml");
@ -61,9 +61,9 @@ add_task(function* test_add() {
is(hiddenOneOffs.length, 1,
"hiddenOneOffs has the correct number of hidden engines present post add.");
is(hiddenOneOffs.includes("FooDupe"), false,
is(hiddenOneOffs.some(x => x == "FooDupe"), false,
"Added engine is not present in hidden list.");
is(hiddenOneOffs.includes("Foo"), true,
is(hiddenOneOffs.some(x => x == "Foo"), true,
"Adding an engine does not remove engines from hidden list.");
});

View File

@ -159,7 +159,12 @@ let openAnimationInspector = Task.async(function*() {
let win = inspector.sidebar.getWindowForTab("animationinspector");
let {AnimationsController, AnimationsPanel} = win;
yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
info("Waiting for the animation controller and panel to be ready");
if (AnimationsPanel.initialized) {
yield AnimationsPanel.initialized;
} else {
yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
}
return {
toolbox: toolbox,

View File

@ -499,7 +499,7 @@
<vbox class="security-info-section">
<hbox id="security-protocol-version"
class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.protocolVersion;"/>
<label id="security-protocol-version-value"
@ -512,7 +512,7 @@
</hbox>
<hbox id="security-ciphersuite"
class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.cipherSuite;"/>
<label id="security-ciphersuite-value"
@ -532,7 +532,7 @@
<vbox class="security-info-section">
<hbox id="security-http-strict-transport-security"
class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.hsts;"/>
<label id="security-http-strict-transport-security-value"
@ -542,7 +542,7 @@
</hbox>
<hbox id="security-public-key-pinning"
class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.security.hpkp;"/>
<label id="security-public-key-pinning-value"
@ -563,7 +563,7 @@
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.cn;:"/>
<label id="security-cert-subject-cn"
@ -572,7 +572,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.o;:"/>
<label id="security-cert-subject-o"
@ -581,7 +581,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.ou;:"/>
<label id="security-cert-subject-ou"
@ -596,7 +596,7 @@
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.cn;:"/>
<label id="security-cert-issuer-cn"
@ -605,7 +605,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.o;:"/>
<label id="security-cert-issuer-o"
@ -614,7 +614,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.ou;:"/>
<label id="security-cert-issuer-ou"
@ -629,7 +629,7 @@
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.begins;:"/>
<label id="security-cert-validity-begins"
@ -638,7 +638,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.expires;:"/>
<label id="security-cert-validity-expires"
@ -653,7 +653,7 @@
</vbox>
<vbox class="security-info-section">
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.sha256fingerprint;:"/>
<label id="security-cert-sha256-fingerprint"
@ -662,7 +662,7 @@
flex="1"/>
</hbox>
<hbox class="tabpanel-summary-container"
align="center">
align="baseline">
<label class="plain tabpanel-summary-label"
value="&certmgr.certdetail.sha1fingerprint;:"/>
<label id="security-cert-sha1-fingerprint"

View File

@ -81,7 +81,11 @@ function memoryActorSupported (target) {
return false;
}
return !!target.getTrait("memoryActorAllocations");
// We need to test that both the root actor has `memoryActorAllocations`,
// which is in Gecko 38+, and also that the target has a memory actor. There
// are scenarios, like addon debugging, where memoryActorAllocations is available,
// but no memory actor (like addon debugging in Gecko 38+)
return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
}
exports.memoryActorSupported = Task.async(memoryActorSupported);

View File

@ -40,7 +40,7 @@ support-files =
[browser_perf-jump-to-debugger-01.js]
[browser_perf-jump-to-debugger-02.js]
[browser_perf-options-01.js]
[browser_perf-options-02.js]
# [browser_perf-options-02.js] bug 1133230
[browser_perf-options-invert-call-tree-01.js]
[browser_perf-options-invert-call-tree-02.js]
[browser_perf-options-invert-flame-graph-01.js]

View File

@ -3,7 +3,7 @@
/**
* Tests that the recording model is populated correctly when using timeline
* and memory actor mocks.
* and memory actor mocks, and the correct views are shown.
*/
const WAIT_TIME = 1000;
@ -14,15 +14,15 @@ let test = Task.async(function*() {
TEST_MOCK_TIMELINE_ACTOR: true
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
ok(memoryMock, "memory should be mocked.");
ok(timelineMock, "timeline should be mocked.");
yield startRecording(panel);
yield startRecording(panel, { waitForOverview: false });
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
yield stopRecording(panel);
yield stopRecording(panel, { waitForOverview: false });
let {
label, duration, markers, frames, memory, ticks, allocations, profile
@ -58,6 +58,23 @@ let test = Task.async(function*() {
ok(sampleCount > 0,
"At least some samples have been iterated over, checking for root nodes.");
is($("#overview-pane").hidden, true,
"overview pane hidden when timeline mocked.");
is($("#select-waterfall-view").hidden, true,
"waterfall view button hidden when timeline mocked");
is($("#select-js-calltree-view").hidden, false,
"jscalltree view button not hidden when timeline/memory mocked");
is($("#select-js-flamegraph-view").hidden, true,
"jsflamegraph view button hidden when timeline mocked");
is($("#select-memory-calltree-view").hidden, true,
"memorycalltree view button hidden when memory mocked");
is($("#select-memory-flamegraph-view").hidden, true,
"memoryflamegraph view button hidden when memory mocked");
ok(DetailsView.isViewSelected(JsCallTreeView),
"JS Call Tree view selected by default when timeline/memory mocked.");
yield teardown(panel);
finish();
});

View File

@ -3,7 +3,7 @@
/**
* Tests that the recording model is populated correctly when using timeline
* and memory actor mocks.
* and memory actor mocks, and that the correct button/overview displays are shown.
*/
const WAIT_TIME = 1000;
@ -13,7 +13,7 @@ let test = Task.async(function*() {
TEST_MOCK_MEMORY_ACTOR: true
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
@ -57,6 +57,23 @@ let test = Task.async(function*() {
ok(sampleCount > 0,
"At least some samples have been iterated over, checking for root nodes.");
is($("#overview-pane").hidden, false,
"overview pane not hidden when only memory mocked.");
is($("#select-waterfall-view").hidden, false,
"waterfall view button not hidden when memory mocked");
is($("#select-js-calltree-view").hidden, false,
"jscalltree view button not hidden when memory mocked");
is($("#select-js-flamegraph-view").hidden, false,
"jsflamegraph view button not hidden when memory mocked");
is($("#select-memory-calltree-view").hidden, true,
"memorycalltree view button hidden when memory mocked");
is($("#select-memory-flamegraph-view").hidden, true,
"memoryflamegraph view button hidden when memory mocked");
ok(DetailsView.isViewSelected(WaterfallView),
"Waterfall view selected by default when memory mocked.");
yield teardown(panel);
finish();
});

View File

@ -266,7 +266,7 @@ function mousedown (win, button) {
EventUtils.sendMouseEvent({ type: "mousedown" }, button, win);
}
function* startRecording(panel) {
function* startRecording(panel, options={}) {
let win = panel.panelWin;
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_START_RECORDING);
let willStart = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_START);
@ -290,7 +290,7 @@ function* startRecording(panel) {
let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
yield hasStarted;
let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
yield stateChanged;
yield overviewRendered;
@ -304,7 +304,7 @@ function* startRecording(panel) {
"The record button should not be locked.");
}
function* stopRecording(panel) {
function* stopRecording(panel, options={}) {
let win = panel.panelWin;
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
@ -328,7 +328,7 @@ function* stopRecording(panel) {
let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
yield hasStopped;
let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
yield stateChanged;
yield overviewRendered;

View File

@ -3,8 +3,6 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DEFAULT_DETAILS_SUBVIEW = "waterfall";
/**
* Details view containing profiler call tree and markers waterfall. Manages
* subviews and toggles visibility between them.
@ -14,11 +12,11 @@ let DetailsView = {
* Name to node+object mapping of subviews.
*/
components: {
"waterfall": { id: "waterfall-view", view: WaterfallView },
"waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView },
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, pref: "enable-memory" },
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, pref: "enable-memory" }
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
},
/**
@ -36,7 +34,7 @@ let DetailsView = {
button.addEventListener("command", this._onViewToggle);
}
yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
yield this.selectDefaultView();
yield this.setAvailableViews();
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
@ -62,22 +60,27 @@ let DetailsView = {
}),
/**
* Sets the possible views based off of prefs by hiding/showing the
* Sets the possible views based off of prefs and server actor support by hiding/showing the
* buttons that select them and going to default view if currently selected.
* Called when a preference changes in `devtools.performance.ui.`.
*/
setAvailableViews: Task.async(function* () {
for (let [name, { view, pref }] of Iterator(this.components)) {
let mocks = gFront.getMocksInUse();
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
let recording = PerformanceController.getCurrentRecording();
let isRecorded = recording && !recording.isRecording();
// View is enabled view prefs
let isEnabled = !pref || PerformanceController.getPref(pref);
$(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !isEnabled;
// View is supported by the server actor, and the requried actor is not being mocked
let isSupported = !requires || requires.every(r => !mocks[r]);
$(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
// If the view is currently selected and not enabled, go back to the
// default view.
if (!isEnabled && this.isViewSelected(view)) {
yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
yield this.selectDefaultView();
}
}
}),
@ -106,6 +109,22 @@ let DetailsView = {
this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
}),
/**
* Selects a default view based off of protocol support
* and preferences enabled.
*/
selectDefaultView: function () {
let { timeline: mockTimeline } = gFront.getMocksInUse();
// If timelines are mocked, the first view available is the js-calltree.
if (mockTimeline) {
return this.selectView("js-calltree");
} else {
// In every other scenario with preferences and mocks, waterfall will
// be the default view.
return this.selectView("waterfall");
}
},
/**
* Checks if the provided view is currently selected.
*

View File

@ -26,6 +26,9 @@ let OverviewView = {
* Sets up the view with event binding.
*/
initialize: function () {
if (gFront.getMocksInUse().timeline) {
this.disable();
}
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
@ -60,6 +63,25 @@ let OverviewView = {
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
},
/**
* Disabled in the event we're using a Timeline mock, so we'll have no
* markers, ticks or memory data to show, so just block rendering and hide
* the panel.
*/
disable: function () {
this._disabled = true;
$("#overview-pane").hidden = true;
},
/**
* Returns the disabled status.
*
* @return boolean
*/
isDisabled: function () {
return this._disabled;
},
/**
* Sets the time interval selection for all graphs in this overview.
*
@ -67,6 +89,10 @@ let OverviewView = {
* The { starTime, endTime }, in milliseconds.
*/
setTimeInterval: function(interval, options = {}) {
if (this.isDisabled()) {
return;
}
let recording = PerformanceController.getCurrentRecording();
if (recording == null) {
throw new Error("A recording should be available in order to set the selection.");
@ -83,10 +109,15 @@ let OverviewView = {
* Gets the time interval selection for all graphs in this overview.
*
* @return object
* The { starTime, endTime }, in milliseconds.
* The { startTime, endTime }, in milliseconds.
*/
getTimeInterval: function() {
let recording = PerformanceController.getCurrentRecording();
if (this.isDisabled()) {
return { startTime: 0, endTime: recording.getDuration() };
}
if (recording == null) {
throw new Error("A recording should be available in order to get the selection.");
}
@ -172,6 +203,9 @@ let OverviewView = {
* The fps graph resolution. @see Graphs.jsm
*/
render: Task.async(function *(resolution) {
if (this.isDisabled()) {
return;
}
let recording = PerformanceController.getCurrentRecording();
let duration = recording.getDuration();
let markers = recording.getMarkers();

View File

@ -5,9 +5,9 @@
const {Cu, Ci} = require("chrome");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
const {Connection} = require("devtools/client/connection-manager");
const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
const {Simulators} = require("devtools/webide/simulators");
const discovery = require("devtools/toolkit/discovery/discovery");
const EventEmitter = require("devtools/toolkit/event-emitter");
const promise = require("promise");
@ -193,14 +193,12 @@ let SimulatorScanner = {
enable() {
this._updateRuntimes = this._updateRuntimes.bind(this);
Simulator.on("register", this._updateRuntimes);
Simulator.on("unregister", this._updateRuntimes);
Simulators.on("updated", this._updateRuntimes);
this._updateRuntimes();
},
disable() {
Simulator.off("register", this._updateRuntimes);
Simulator.off("unregister", this._updateRuntimes);
Simulators.off("updated", this._updateRuntimes);
},
_emitUpdated() {
@ -208,11 +206,13 @@ let SimulatorScanner = {
},
_updateRuntimes() {
this._runtimes = [];
for (let name of Simulator.availableNames()) {
this._runtimes.push(new SimulatorRuntime(name));
}
this._emitUpdated();
Simulators.getAll().then(simulators => {
this._runtimes = [];
for (let simulator of simulators) {
this._runtimes.push(new SimulatorRuntime(simulator));
}
this._emitUpdated();
});
},
scan() {
@ -542,28 +542,26 @@ WiFiRuntime.prototype = {
// For testing use only
exports._WiFiRuntime = WiFiRuntime;
function SimulatorRuntime(name) {
this.name = name;
function SimulatorRuntime(simulator) {
this.simulator = simulator;
}
SimulatorRuntime.prototype = {
type: RuntimeTypes.SIMULATOR,
connect: function(connection) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByName(this.name);
if (!simulator || !simulator.launch) {
return promise.reject(new Error("Can't find simulator: " + this.name));
}
return simulator.launch({port: port}).then(() => {
return this.simulator.launch().then(port => {
connection.host = "localhost";
connection.port = port;
connection.keepConnecting = true;
connection.once(Connection.Events.DISCONNECTED, simulator.close);
connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
connection.connect();
});
},
get id() {
return this.name;
return this.simulator.id;
},
get name() {
return this.simulator.name;
},
};

View File

@ -0,0 +1,272 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
'use strict';
const { Cc, Ci, Cu } = require("chrome");
const Environment = require("sdk/system/environment").env;
const Subprocess = require("sdk/system/child_process/subprocess");
const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
let OS = "";
if (platform.indexOf("Win") != -1) {
OS = "win32";
} else if (platform.indexOf("Mac") != -1) {
OS = "mac64";
} else if (platform.indexOf("Linux") != -1) {
if (platform.indexOf("x86_64") != -1) {
OS = "linux64";
} else {
OS = "linux32";
}
}
function SimulatorProcess() {}
SimulatorProcess.prototype = {
// Check if B2G is running.
get isRunning() !!this.process,
// Start the process and connect the debugger client.
run() {
// Resolve B2G binary.
let b2g = this.b2gBinary;
if (!b2g || !b2g.exists()) {
throw Error("B2G executable not found.");
}
this.once("stdout", function () {
if (OS == "mac64") {
console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
// Escape double quotes and escape characters for use in AppleScript.
let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
Subprocess.call({
command: "/usr/bin/osascript",
arguments: ["-e", 'tell application "' + path + '" to activate'],
});
}
});
this.on("stdout", (e, data) => this.log(e, data.trim()));
this.on("stderr", (e, data) => this.log(e, data.trim()));
let environment;
if (OS.indexOf("linux") > -1) {
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
if ("DISPLAY" in Environment) {
environment.push("DISPLAY=" + Environment.DISPLAY);
}
}
// Spawn a B2G instance.
this.process = Subprocess.call({
command: b2g,
arguments: this.args,
environment: environment,
stdout: data => this.emit("stdout", data),
stderr: data => this.emit("stderr", data),
// On B2G instance exit, reset tracked process, remote debugger port and
// shuttingDown flag, then finally emit an exit event.
done: result => {
console.log("B2G terminated with " + result.exitCode);
this.process = null;
this.emit("exit", result.exitCode);
}
});
},
// Request a B2G instance kill.
kill() {
let deferred = promise.defer();
if (this.process) {
this.once("exit", (e, exitCode) => {
this.shuttingDown = false;
deferred.resolve(exitCode);
});
if (!this.shuttingDown) {
this.shuttingDown = true;
this.emit("kill", null);
this.process.kill();
}
return deferred.promise;
} else {
return promise.resolve(undefined);
}
},
// Maybe log output messages.
log(level, message) {
if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
return;
}
if (level === "stderr" || level === "error") {
console.error(message);
return;
}
console.log(message);
},
// Compute B2G CLI arguments.
get args() {
let args = [];
let gaia = this.gaiaProfile;
if (!gaia || !gaia.exists()) {
throw Error("Gaia profile directory not found.");
}
args.push("-profile", gaia.path);
args.push("-start-debugger-server", "" + this.options.port);
// Ignore eventual zombie instances of b2g that are left over.
args.push("-no-remote");
return args;
},
};
EventEmitter.decorate(SimulatorProcess.prototype);
function CustomSimulatorProcess(options) {
this.options = options;
}
let CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(CSPp, "b2gBinary", {
get: function() {
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
file.initWithPath(this.options.b2gBinary);
return file;
}
});
// Compute Gaia profile file handle.
Object.defineProperty(CSPp, "gaiaProfile", {
get: function() {
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
file.initWithPath(this.options.gaiaProfile);
return file;
}
});
exports.CustomSimulatorProcess = CustomSimulatorProcess;
function AddonSimulatorProcess(addon, options) {
this.addon = addon;
this.options = options;
}
let ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(ASPp, "b2gBinary", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".customRuntime";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
file.append("b2g");
let binaries = {
win32: "b2g-bin.exe",
mac64: "B2G.app/Contents/MacOS/b2g-bin",
linux32: "b2g-bin",
linux64: "b2g-bin",
};
binaries[OS].split("/").forEach(node => file.append(node));
}
return file;
}
});
// Compute Gaia profile file handle.
Object.defineProperty(ASPp, "gaiaProfile", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".gaiaProfile";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
file.append("profile");
}
return file;
}
});
exports.AddonSimulatorProcess = AddonSimulatorProcess;
function OldAddonSimulatorProcess(addon, options) {
this.addon = addon;
this.options = options;
}
let OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(OASPp, "b2gBinary", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".customRuntime";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
file.append("resources");
file.append("fxos_" + version + "_simulator");
file.append("data");
file.append(OS == "linux32" ? "linux" : OS);
let binaries = {
win32: "b2g/b2g-bin.exe",
mac64: "B2G.app/Contents/MacOS/b2g-bin",
linux32: "b2g/b2g-bin",
linux64: "b2g/b2g-bin",
};
binaries[OS].split("/").forEach(node => file.append(node));
}
return file;
}
});
// Compute B2G CLI arguments.
Object.defineProperty(OASPp, "args", {
get: function() {
let args = [];
let gaia = this.gaiaProfile;
if (!gaia || !gaia.exists()) {
throw Error("Gaia profile directory not found.");
}
args.push("-profile", gaia.path);
args.push("-dbgport", "" + this.options.port);
// Ignore eventual zombie instances of b2g that are left over.
args.push("-no-remote");
return args;
}
});
exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;

View File

@ -0,0 +1,96 @@
/* 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/. */
const { Cu } = require("chrome");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const { ConnectionManager } = require("devtools/client/connection-manager");
const { AddonSimulatorProcess, OldAddonSimulatorProcess } = require("devtools/webide/simulator-process");
const promise = require("promise");
const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
let Simulators = {
// TODO (Bug 1090949) Don't generate this list from installed simulator
// addons, but instead implement a persistent list of user-configured
// simulators.
getAll() {
let deferred = promise.defer();
AddonManager.getAllAddons(addons => {
let simulators = [];
for (let addon of addons) {
if (SimulatorRegExp.exec(addon.id)) {
simulators.push(new Simulator(addon));
}
}
// Sort simulators alphabetically by name.
simulators.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
});
deferred.resolve(simulators);
});
return deferred.promise;
},
}
EventEmitter.decorate(Simulators);
exports.Simulators = Simulators;
function update() {
Simulators.emit("updated");
}
AddonManager.addAddonListener({
onEnabled: update,
onDisabled: update,
onInstalled: update,
onUninstalled: update
});
function Simulator(addon) {
this.addon = addon;
}
Simulator.prototype = {
launch() {
// Close already opened simulation.
if (this.process) {
return this.kill().then(this.launch.bind(this));
}
let options = {
port: ConnectionManager.getFreeTCPPort()
};
if (this.version <= "1.3") {
// Support older simulator addons.
this.process = new OldAddonSimulatorProcess(this.addon, options);
} else {
this.process = new AddonSimulatorProcess(this.addon, options);
}
this.process.run();
return promise.resolve(options.port);
},
kill() {
let process = this.process;
if (!process) {
return promise.resolve();
}
this.process = null;
return process.kill();
},
get id() {
return this.addon.id;
},
get name() {
return this.addon.name.replace(" Simulator", "");
},
get version() {
return this.name.match(/\d+\.\d+/)[0];
},
};

View File

@ -20,6 +20,8 @@ EXTRA_JS_MODULES.devtools.webide += [
'modules/config-view.js',
'modules/remote-resources.js',
'modules/runtimes.js',
'modules/simulator-process.js',
'modules/simulators.js',
'modules/tab-store.js',
'modules/utils.js'
]

View File

@ -13,6 +13,7 @@ pref("devtools.webide.enableLocalRuntime", false);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\.org$");
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
@ -20,6 +21,7 @@ pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
pref("devtools.webide.lastConnectedRuntime", "");
pref("devtools.webide.lastSelectedProject", "");
pref("devtools.webide.logSimulatorOutput", false);
pref("devtools.webide.widget.autoinstall", true);
#ifdef MOZ_DEV_EDITION
pref("devtools.webide.widget.enabled", true);

View File

@ -837,3 +837,6 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY processHang.terminatePlugin.accessKey "P">
<!ENTITY processHang.terminateProcess.label "Kill Web Process">
<!ENTITY processHang.terminateProcess.accessKey "K">
<!ENTITY emeLearnMoreContextMenu.label "Learn more about DRM…">
<!ENTITY emeLearnMoreContextMenu.accesskey "D">

View File

@ -40,6 +40,17 @@ addonDownloadRestart=Restart Download;Restart Downloads
addonDownloadRestart.accessKey=R
addonDownloadCancelTooltip=Cancel
addonwatch.slow=%S might be making %S run slowly
addonwatch.disable.label=Disable %S
addonwatch.disable.accesskey=D
addonwatch.ignoreSession.label=Ignore for now
addonwatch.ignoreSession.accesskey=I
addonwatch.ignorePerm.label=Ignore permanently
addonwatch.ignorePerm.accesskey=p
addonwatch.restart.message=To disable %S you must restart %S
addonwatch.restart.label=Restart %s
addonwatch.restart.accesskey=R
# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
# Semicolon-separated list of plural forms. See:
# http://developer.mozilla.org/en/docs/Localization_and_Plurals

View File

@ -669,6 +669,7 @@ this.BrowserUITelemetry = {
"spell-add-dictionaries-main", "spell-dictionaries",
"spell-dictionaries-menu", "spell-add-dictionaries",
"bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
"media-eme-learn-more"
]),
_contextMenuInteractions: {},

View File

@ -11,7 +11,7 @@
treechildren::-moz-tree-image(icon),
treechildren::-moz-tree-image(noicon) {
padding-right: 2px;
margin: 0px 2px;
margin: 0 2px;
width: 16px;
height: 16px;
}

View File

@ -20,3 +20,32 @@
#tabList[available] {
display: -moz-box;
}
treechildren::-moz-tree-image(icon),
treechildren::-moz-tree-image(noicon) {
padding-right: 2px;
margin: 0 2px;
width: 16px;
height: 16px;
}
treechildren::-moz-tree-image(noicon) {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
}
treechildren::-moz-tree-image(container, noicon) {
list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
}
treechildren::-moz-tree-image(checked) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check");
}
treechildren::-moz-tree-image(checked, selected) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
}
treechildren::-moz-tree-image(partial) {
list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
}
treechildren::-moz-tree-image(partial, selected) {
list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
}

View File

@ -79,3 +79,7 @@
height: 16px;
margin: 7px;
}
#context-media-eme-learnmore {
list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
}

View File

@ -350,6 +350,11 @@ description > html|a {
width: 66ch;
}
.largeDialogContainer.doScroll {
overflow-y: auto;
-moz-box-flex: 1;
}
/**
* End Dialog
*/

View File

@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="22 22 16 16" enable-background="new 22 22 16 16" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
<stop offset="0" style="stop-color:#E63B2E"/>
<stop offset="1" style="stop-color:#C33931"/>
</linearGradient>
<circle fill="url(#SVGID_1_)" cx="30" cy="30" r="7"/>
<g>
<path fill="#FFFFFF" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092
c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z
"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="22 22 16 16">
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
<stop offset="0" style="stop-color: #e63b2e"/>
<stop offset="1" style="stop-color: #c33931"/>
</linearGradient>
<circle fill="url(#gradient)" cx="30" cy="30" r="7"/>
<path fill="#fff" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092 c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z"/>
</svg>

Before

Width:  |  Height:  |  Size: 970 B

After

Width:  |  Height:  |  Size: 675 B

View File

@ -708,6 +708,7 @@ GK_ATOM(onconfigurationchange, "onconfigurationchange")
GK_ATOM(onconnect, "onconnect")
GK_ATOM(onconnected, "onconnected")
GK_ATOM(onconnecting, "onconnecting")
GK_ATOM(onconnectionstatechanged, "onconnectionstatechanged")
GK_ATOM(oncontextmenu, "oncontextmenu")
GK_ATOM(oncopy, "oncopy")
GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")

View File

@ -152,6 +152,10 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
},
'BluetoothGatt': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
},
'BluetoothManager': {
'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
},

View File

@ -196,6 +196,12 @@ extern bool gBluetoothDebugFlag;
*/
#define REQUEST_MEDIA_PLAYSTATUS_ID "requestmediaplaystatus"
/**
* When a remote BLE device gets connected / disconnected, we'll dispatch an
* event
*/
#define GATT_CONNECTION_STATE_CHANGED_ID "connectionstatechanged"
// Bluetooth address format: xx:xx:xx:xx:xx:xx (or xx_xx_xx_xx_xx_xx)
#define BLUETOOTH_ADDRESS_LENGTH 17
#define BLUETOOTH_ADDRESS_NONE "00:00:00:00:00:00"

View File

@ -4,15 +4,16 @@
* 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 "BluetoothClassOfDevice.h"
#include "BluetoothDevice.h"
#include "BluetoothReplyRunnable.h"
#include "BluetoothService.h"
#include "BluetoothUtils.h"
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/dom/BluetoothAttributeEvent.h"
#include "mozilla/dom/BluetoothDevice2Binding.h"
#include "mozilla/dom/bluetooth/BluetoothClassOfDevice.h"
#include "mozilla/dom/bluetooth/BluetoothDevice.h"
#include "mozilla/dom/bluetooth/BluetoothGatt.h"
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/dom/Promise.h"
using namespace mozilla;
@ -20,7 +21,10 @@ using namespace mozilla::dom;
USING_BLUETOOTH_NAMESPACE
NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice, DOMEventTargetHelper, mCod)
NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice,
DOMEventTargetHelper,
mCod,
mGatt)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDevice)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@ -302,6 +306,19 @@ BluetoothDevice::DispatchAttributeEvent(const nsTArray<nsString>& aTypes)
DispatchTrustedEvent(event);
}
BluetoothGatt*
BluetoothDevice::GetGatt()
{
NS_ENSURE_TRUE(mType == BluetoothDeviceType::Le ||
mType == BluetoothDeviceType::Dual,
nullptr);
if (!mGatt) {
mGatt = new BluetoothGatt(GetOwner(), mAddress);
}
return mGatt;
}
JSObject*
BluetoothDevice::WrapObject(JSContext* aContext)
{

View File

@ -10,7 +10,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/BluetoothDevice2Binding.h"
#include "BluetoothCommon.h"
#include "mozilla/dom/bluetooth/BluetoothCommon.h"
#include "nsString.h"
#include "nsCOMPtr.h"
@ -23,6 +23,7 @@ namespace dom {
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothClassOfDevice;
class BluetoothGatt;
class BluetoothNamedValue;
class BluetoothValue;
class BluetoothSignal;
@ -69,6 +70,8 @@ public:
return mType;
}
BluetoothGatt* GetGatt();
/****************************************************************************
* Event Handlers
***************************************************************************/
@ -173,6 +176,11 @@ private:
* Type of this device. Can be unknown/classic/le/dual.
*/
BluetoothDeviceType mType;
/**
* GATT client object to interact with the remote device.
*/
nsRefPtr<BluetoothGatt> mGatt;
};
END_BLUETOOTH_NAMESPACE

View File

@ -0,0 +1,217 @@
/* -*- 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 "BluetoothReplyRunnable.h"
#include "BluetoothService.h"
#include "BluetoothUtils.h"
#include "mozilla/dom/bluetooth/BluetoothGatt.h"
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/dom/BluetoothGattBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsIUUIDGenerator.h"
#include "nsServiceManagerUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
USING_BLUETOOTH_NAMESPACE
NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGatt,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGatt,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGatt)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(BluetoothGatt, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(BluetoothGatt, DOMEventTargetHelper)
BluetoothGatt::BluetoothGatt(nsPIDOMWindow* aWindow,
const nsAString& aDeviceAddr)
: DOMEventTargetHelper(aWindow)
, mAppUuid(EmptyString())
, mClientIf(0)
, mConnectionState(BluetoothConnectionState::Disconnected)
, mDeviceAddr(aDeviceAddr)
{
MOZ_ASSERT(aWindow);
MOZ_ASSERT(!mDeviceAddr.IsEmpty());
}
BluetoothGatt::~BluetoothGatt()
{
BluetoothService* bs = BluetoothService::Get();
// bs can be null on shutdown, where destruction might happen.
NS_ENSURE_TRUE_VOID(bs);
if (mClientIf > 0) {
nsRefPtr<BluetoothVoidReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr);
bs->UnregisterGattClientInternal(mClientIf, result);
}
bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
}
void
BluetoothGatt::GenerateUuid(nsAString &aUuidString)
{
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
NS_ENSURE_SUCCESS_VOID(rv);
nsID uuid;
rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
NS_ENSURE_SUCCESS_VOID(rv);
// Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
char uuidBuffer[NSID_LENGTH];
uuid.ToProvidedString(uuidBuffer);
NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
// Remove {} and the null terminator
aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
}
void
BluetoothGatt::DisconnectFromOwner()
{
DOMEventTargetHelper::DisconnectFromOwner();
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
if (mClientIf > 0) {
nsRefPtr<BluetoothVoidReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr);
bs->UnregisterGattClientInternal(mClientIf, result);
}
bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
}
already_AddRefed<Promise>
BluetoothGatt::Connect(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
BT_ENSURE_TRUE_REJECT(
mConnectionState == BluetoothConnectionState::Disconnected,
NS_ERROR_DOM_INVALID_STATE_ERR);
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
if (mAppUuid.IsEmpty()) {
GenerateUuid(mAppUuid);
BT_ENSURE_TRUE_REJECT(!mAppUuid.IsEmpty(),
NS_ERROR_DOM_OPERATION_ERR);
bs->RegisterBluetoothSignalHandler(mAppUuid, this);
}
UpdateConnectionState(BluetoothConnectionState::Connecting);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("ConnectGattClient"));
bs->ConnectGattClientInternal(mAppUuid,
mDeviceAddr,
result);
return promise.forget();
}
already_AddRefed<Promise>
BluetoothGatt::Disconnect(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
BT_ENSURE_TRUE_REJECT(
mConnectionState == BluetoothConnectionState::Connected,
NS_ERROR_DOM_INVALID_STATE_ERR);
BluetoothService* bs = BluetoothService::Get();
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
UpdateConnectionState(BluetoothConnectionState::Disconnecting);
nsRefPtr<BluetoothReplyRunnable> result =
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
promise,
NS_LITERAL_STRING("DisconnectGattClient"));
bs->DisconnectGattClientInternal(mAppUuid, mDeviceAddr, result);
return promise.forget();
}
void
BluetoothGatt::UpdateConnectionState(BluetoothConnectionState aState)
{
BT_API2_LOGR("GATT connection state changes to: %d", int(aState));
mConnectionState = aState;
// Dispatch connectionstatechanged event to application
nsCOMPtr<nsIDOMEvent> event;
nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
NS_ENSURE_SUCCESS_VOID(rv);
rv = event->InitEvent(NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
false,
false);
NS_ENSURE_SUCCESS_VOID(rv);
DispatchTrustedEvent(event);
}
void
BluetoothGatt::Notify(const BluetoothSignal& aData)
{
BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
BluetoothValue v = aData.value();
if (aData.name().EqualsLiteral("ClientRegistered")) {
MOZ_ASSERT(v.type() == BluetoothValue::Tuint32_t);
mClientIf = v.get_uint32_t();
} else if (aData.name().EqualsLiteral("ClientUnregistered")) {
mClientIf = 0;
} else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
BluetoothConnectionState state =
v.get_bool() ? BluetoothConnectionState::Connected
: BluetoothConnectionState::Disconnected;
UpdateConnectionState(state);
} else {
BT_WARNING("Not handling GATT signal: %s",
NS_ConvertUTF16toUTF8(aData.name()).get());
}
}
JSObject*
BluetoothGatt::WrapObject(JSContext* aContext)
{
return BluetoothGattBinding::Wrap(aContext, this);
}

View File

@ -0,0 +1,116 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_bluetooth_bluetoothgatt_h__
#define mozilla_dom_bluetooth_bluetoothgatt_h__
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/BluetoothGattBinding.h"
#include "mozilla/dom/bluetooth/BluetoothCommon.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace dom {
class Promise;
}
}
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothReplyRunnable;
class BluetoothService;
class BluetoothSignal;
class BluetoothValue;
class BluetoothGatt MOZ_FINAL : public DOMEventTargetHelper
, public BluetoothSignalObserver
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothGatt, DOMEventTargetHelper)
/****************************************************************************
* Attribute Getters
***************************************************************************/
BluetoothConnectionState ConnectionState() const
{
return mConnectionState;
}
/****************************************************************************
* Event Handlers
***************************************************************************/
IMPL_EVENT_HANDLER(connectionstatechanged);
/****************************************************************************
* Methods (Web API Implementation)
***************************************************************************/
already_AddRefed<Promise> Connect(ErrorResult& aRv);
already_AddRefed<Promise> Disconnect(ErrorResult& aRv);
/****************************************************************************
* Others
***************************************************************************/
void Notify(const BluetoothSignal& aParam); // BluetoothSignalObserver
nsPIDOMWindow* GetParentObject() const
{
return GetOwner();
}
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
virtual void DisconnectFromOwner() MOZ_OVERRIDE;
BluetoothGatt(nsPIDOMWindow* aOwner,
const nsAString& aDeviceAddr);
private:
~BluetoothGatt();
/**
* Update mConnectionState to aState and fire
* connectionstatechanged event to the application.
*
* @param aState [in] New connection state
*/
void UpdateConnectionState(BluetoothConnectionState aState);
/**
* Generate a random uuid.
*
* @param aUuidString [out] String to store the generated uuid.
*/
void GenerateUuid(nsAString &aUuidString);
/****************************************************************************
* Variables
***************************************************************************/
/**
* Random generated UUID of this GATT client.
*/
nsString mAppUuid;
/**
* Id of the GATT client interface given by bluetooth stack.
* 0 if the client is not registered yet, nonzero otherwise.
*/
int mClientIf;
/**
* Connection state of this remote device.
*/
BluetoothConnectionState mConnectionState;
/**
* Address of the remote device.
*/
nsString mDeviceAddr;
};
END_BLUETOOTH_NAMESPACE
#endif

View File

@ -307,6 +307,31 @@ public:
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) = 0;
/**
* Connect to a remote GATT server. (platform specific implementation)
*/
virtual void
ConnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) = 0;
/**
* Disconnect GATT client from a remote GATT server.
* (platform specific implementation)
*/
virtual void
DisconnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) = 0;
/**
* Unregister a GATT client. (platform specific implementation)
*/
virtual void
UnregisterGattClientInternal(int aClientIf,
BluetoothReplyRunnable* aRunnable) = 0;
bool
IsEnabled() const
{

View File

@ -40,6 +40,30 @@ UuidToString(const BluetoothUuid& aUuid, nsAString& aString)
aString.AssignLiteral(uuidStr);
}
void
StringToUuid(const char* aString, BluetoothUuid& aUuid)
{
uint32_t uuid0, uuid4;
uint16_t uuid1, uuid2, uuid3, uuid5;
sscanf(aString, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
&uuid0, &uuid1, &uuid2, &uuid3, &uuid4, &uuid5);
uuid0 = htonl(uuid0);
uuid1 = htons(uuid1);
uuid2 = htons(uuid2);
uuid3 = htons(uuid3);
uuid4 = htonl(uuid4);
uuid5 = htons(uuid5);
memcpy(&aUuid.mUuid[0], &uuid0, 4);
memcpy(&aUuid.mUuid[4], &uuid1, 2);
memcpy(&aUuid.mUuid[6], &uuid2, 2);
memcpy(&aUuid.mUuid[8], &uuid3, 2);
memcpy(&aUuid.mUuid[10], &uuid4, 4);
memcpy(&aUuid.mUuid[14], &uuid5, 2);
}
bool
SetJsObject(JSContext* aContext,
const BluetoothValue& aValue,

View File

@ -19,6 +19,14 @@ class BluetoothReplyRunnable;
void
UuidToString(const BluetoothUuid& aUuid, nsAString& aString);
/**
* Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
* This utility function is used by gecko internal only to convert uuid string
* created by gecko back to BluetoothUuid representation.
*/
void
StringToUuid(const char* aString, BluetoothUuid& aUuid);
bool
SetJsObject(JSContext* aContext,
const BluetoothValue& aValue,

View File

@ -5,16 +5,28 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BluetoothGattManager.h"
#include "BluetoothCommon.h"
#include "BluetoothUtils.h"
#include "BluetoothInterface.h"
#include "BluetoothCommon.h"
#include "BluetoothInterface.h"
#include "BluetoothReplyRunnable.h"
#include "BluetoothService.h"
#include "BluetoothUtils.h"
#include "MainThreadUtils.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "MainThreadUtils.h"
#include "nsIObserverService.h"
#include "nsThreadUtils.h"
#define ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(runnable) \
do { \
if (!sBluetoothGattInterface) { \
NS_NAMED_LITERAL_STRING(errorStr, \
"BluetoothGattClientInterface is not ready"); \
DispatchBluetoothReply(runnable, BluetoothValue(), errorStr); \
return; \
} \
} while(0)
using namespace mozilla;
USING_BLUETOOTH_NAMESPACE
@ -26,9 +38,58 @@ namespace {
bool BluetoothGattManager::mInShutdown = false;
/*
* Static functions
*/
class BluetoothGattClient;
static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
class BluetoothGattClient MOZ_FINAL : public nsISupports
{
public:
NS_DECL_ISUPPORTS
BluetoothGattClient(const nsAString& aAppUuid, const nsAString& aDeviceAddr)
: mAppUuid(aAppUuid)
, mDeviceAddr(aDeviceAddr)
, mClientIf(0)
, mConnId(0)
{ }
~BluetoothGattClient()
{
mConnectRunnable = nullptr;
mDisconnectRunnable = nullptr;
mUnregisterClientRunnable = nullptr;
}
nsString mAppUuid;
nsString mDeviceAddr;
int mClientIf;
int mConnId;
nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
};
NS_IMPL_ISUPPORTS0(BluetoothGattClient)
class UuidComparator
{
public:
bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
const nsAString& aAppUuid) const
{
return aClient->mAppUuid.Equals(aAppUuid);
}
};
class ClientIfComparator
{
public:
bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
int aClientIf) const
{
return aClient->mClientIf == aClientIf;
}
};
BluetoothGattManager*
BluetoothGattManager::Get()
@ -103,6 +164,10 @@ BluetoothGattManager::InitGattInterface(BluetoothProfileResultHandler* aRes)
sBluetoothGattInterface->GetBluetoothGattClientInterface();
NS_ENSURE_TRUE_VOID(sBluetoothGattClientInterface);
if (!sClients) {
sClients = new nsTArray<nsRefPtr<BluetoothGattClient> >;
}
BluetoothGattManager* gattManager = BluetoothGattManager::Get();
sBluetoothGattInterface->Init(gattManager,
new InitGattResultHandler(aRes));
@ -129,6 +194,8 @@ public:
{
sBluetoothGattClientInterface = nullptr;
sBluetoothGattInterface = nullptr;
sClients = nullptr;
if (mRes) {
mRes->Deinit();
}
@ -176,6 +243,265 @@ BluetoothGattManager::DeinitGattInterface(BluetoothProfileResultHandler* aRes)
}
}
class BluetoothGattManager::RegisterClientResultHandler MOZ_FINAL
: public BluetoothGattClientResultHandler
{
public:
RegisterClientResultHandler(BluetoothGattClient* aClient)
: mClient(aClient)
{
MOZ_ASSERT(mClient);
}
void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
{
BT_WARNING("BluetoothGattClientInterface::RegisterClient failed: %d",
(int)aStatus);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
// Notify BluetoothGatt for client disconnected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
mClient->mAppUuid,
BluetoothValue(false)); // Disconnected
bs->DistributeSignal(signal);
// Reject the connect request
if (mClient->mConnectRunnable) {
NS_NAMED_LITERAL_STRING(errorStr, "Register GATT client failed");
DispatchBluetoothReply(mClient->mConnectRunnable,
BluetoothValue(),
errorStr);
mClient->mConnectRunnable = nullptr;
}
sClients->RemoveElement(mClient);
}
private:
nsRefPtr<BluetoothGattClient> mClient;
};
class BluetoothGattManager::UnregisterClientResultHandler MOZ_FINAL
: public BluetoothGattClientResultHandler
{
public:
UnregisterClientResultHandler(BluetoothGattClient* aClient)
: mClient(aClient)
{
MOZ_ASSERT(mClient);
}
void UnregisterClient() MOZ_OVERRIDE
{
MOZ_ASSERT(mClient->mUnregisterClientRunnable);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
// Notify BluetoothGatt to clear the clientIf
BluetoothSignal signal(
NS_LITERAL_STRING("ClientUnregistered"),
mClient->mAppUuid,
BluetoothValue(true));
bs->DistributeSignal(signal);
// Resolve the unregister request
DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
BluetoothValue(true),
EmptyString());
mClient->mUnregisterClientRunnable = nullptr;
sClients->RemoveElement(mClient);
}
void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
{
BT_WARNING("BluetoothGattClientInterface::UnregisterClient failed: %d",
(int)aStatus);
MOZ_ASSERT(mClient->mUnregisterClientRunnable);
// Reject the unregister request
NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
BluetoothValue(),
errorStr);
mClient->mUnregisterClientRunnable = nullptr;
}
private:
nsRefPtr<BluetoothGattClient> mClient;
};
void
BluetoothGattManager::UnregisterClient(int aClientIf,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRunnable);
ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
ClientIfComparator());
// Reject the unregister request if the client is not found
if (index == sClients->NoIndex) {
NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
DispatchBluetoothReply(aRunnable,
BluetoothValue(),
errorStr);
return;
}
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
client->mUnregisterClientRunnable = aRunnable;
sBluetoothGattClientInterface->UnregisterClient(
aClientIf,
new UnregisterClientResultHandler(client));
}
class BluetoothGattManager::ConnectResultHandler MOZ_FINAL
: public BluetoothGattClientResultHandler
{
public:
ConnectResultHandler(BluetoothGattClient* aClient)
: mClient(aClient)
{
MOZ_ASSERT(mClient);
}
void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
{
BT_WARNING("BluetoothGattClientInterface::Connect failed: %d",
(int)aStatus);
MOZ_ASSERT(mClient->mConnectRunnable);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
// Notify BluetoothGatt for client disconnected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
mClient->mAppUuid,
BluetoothValue(false)); // Disconnected
bs->DistributeSignal(signal);
// Reject the connect request
NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
DispatchBluetoothReply(mClient->mConnectRunnable,
BluetoothValue(),
errorStr);
mClient->mConnectRunnable = nullptr;
}
private:
nsRefPtr<BluetoothGattClient> mClient;
};
void
BluetoothGattManager::Connect(const nsAString& aAppUuid,
const nsAString& aDeviceAddr,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRunnable);
ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
if (index == sClients->NoIndex) {
index = sClients->Length();
sClients->AppendElement(new BluetoothGattClient(aAppUuid, aDeviceAddr));
}
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
client->mConnectRunnable = aRunnable;
if (client->mClientIf > 0) {
sBluetoothGattClientInterface->Connect(client->mClientIf,
aDeviceAddr,
true, // direct connect
new ConnectResultHandler(client));
} else {
BluetoothUuid uuid;
StringToUuid(NS_ConvertUTF16toUTF8(aAppUuid).get(), uuid);
// connect will be proceeded after client registered
sBluetoothGattClientInterface->RegisterClient(
uuid, new RegisterClientResultHandler(client));
}
}
class BluetoothGattManager::DisconnectResultHandler MOZ_FINAL
: public BluetoothGattClientResultHandler
{
public:
DisconnectResultHandler(BluetoothGattClient* aClient)
: mClient(aClient)
{
MOZ_ASSERT(mClient);
}
void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
{
BT_WARNING("BluetoothGattClientInterface::Disconnect failed: %d",
(int)aStatus);
MOZ_ASSERT(mClient->mDisconnectRunnable);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
// Notify BluetoothGatt that the client remains connected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
mClient->mAppUuid,
BluetoothValue(true)); // Connected
bs->DistributeSignal(signal);
// Reject the disconnect request
NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
DispatchBluetoothReply(mClient->mDisconnectRunnable,
BluetoothValue(),
errorStr);
mClient->mDisconnectRunnable = nullptr;
}
private:
nsRefPtr<BluetoothGattClient> mClient;
};
void
BluetoothGattManager::Disconnect(const nsAString& aAppUuid,
const nsAString& aDeviceAddr,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRunnable);
ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
// Reject the disconnect request if the client is not found
if (index == sClients->NoIndex) {
NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return;
}
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
client->mDisconnectRunnable = aRunnable;
sBluetoothGattClientInterface->Disconnect(
client->mClientIf,
aDeviceAddr,
client->mConnId,
new DisconnectResultHandler(client));
}
//
// Notification Handlers
//
@ -183,7 +509,60 @@ void
BluetoothGattManager::RegisterClientNotification(int aStatus,
int aClientIf,
const BluetoothUuid& aAppUuid)
{ }
{
BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
MOZ_ASSERT(NS_IsMainThread());
nsString uuid;
UuidToString(aAppUuid, uuid);
size_t index = sClients->IndexOf(uuid, 0 /* Start */, UuidComparator());
NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
if (aStatus) { // operation failed
BT_API2_LOGR(
"RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
// Notify BluetoothGatt for client disconnected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
uuid, BluetoothValue(false)); // Disconnected
bs->DistributeSignal(signal);
// Reject the connect request
if (client->mConnectRunnable) {
NS_NAMED_LITERAL_STRING(errorStr,
"Connect failed due to registration failed");
DispatchBluetoothReply(client->mConnectRunnable,
BluetoothValue(),
errorStr);
client->mConnectRunnable = nullptr;
}
sClients->RemoveElement(client);
return;
}
client->mClientIf = aClientIf;
// Notify BluetoothGatt to update the clientIf
BluetoothSignal signal(
NS_LITERAL_STRING("ClientRegistered"),
uuid, BluetoothValue(uint32_t(aClientIf)));
bs->DistributeSignal(signal);
// Client just registered, proceed remaining connect request.
if (client->mConnectRunnable) {
sBluetoothGattClientInterface->Connect(
aClientIf, client->mDeviceAddr, true /* direct connect */,
new ConnectResultHandler(client));
}
}
void
BluetoothGattManager::ScanResultNotification(
@ -195,15 +574,114 @@ void
BluetoothGattManager::ConnectNotification(int aConnId,
int aStatus,
int aClientIf,
const nsAString& aBdAddr)
{ }
const nsAString& aDeviceAddr)
{
BT_API2_LOGR();
MOZ_ASSERT(NS_IsMainThread());
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
ClientIfComparator());
NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
if (aStatus) { // operation failed
BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
aClientIf, aConnId, aStatus);
// Notify BluetoothGatt that the client remains disconnected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
client->mAppUuid,
BluetoothValue(false)); // Disconnected
bs->DistributeSignal(signal);
// Reject the connect request
if (client->mConnectRunnable) {
NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
DispatchBluetoothReply(client->mConnectRunnable,
BluetoothValue(),
errorStr);
client->mConnectRunnable = nullptr;
}
return;
}
client->mConnId = aConnId;
// Notify BluetoothGatt for client connected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
client->mAppUuid,
BluetoothValue(true)); // Connected
bs->DistributeSignal(signal);
// Resolve the connect request
if (client->mConnectRunnable) {
DispatchBluetoothReply(client->mConnectRunnable,
BluetoothValue(true),
EmptyString());
client->mConnectRunnable = nullptr;
}
}
void
BluetoothGattManager::DisconnectNotification(int aConnId,
int aStatus,
int aClientIf,
const nsAString& aBdAddr)
{ }
const nsAString& aDeviceAddr)
{
BT_API2_LOGR();
MOZ_ASSERT(NS_IsMainThread());
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE_VOID(bs);
size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
ClientIfComparator());
NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
if (aStatus) { // operation failed
// Notify BluetoothGatt that the client remains connected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
client->mAppUuid,
BluetoothValue(true)); // Connected
bs->DistributeSignal(signal);
// Reject the disconnect request
if (client->mDisconnectRunnable) {
NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
DispatchBluetoothReply(client->mDisconnectRunnable,
BluetoothValue(),
errorStr);
client->mDisconnectRunnable = nullptr;
}
return;
}
client->mConnId = 0;
// Notify BluetoothGatt for client disconnected
BluetoothSignal signal(
NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
client->mAppUuid,
BluetoothValue(false)); // Disconnected
bs->DistributeSignal(signal);
// Resolve the disconnect request
if (client->mDisconnectRunnable) {
DispatchBluetoothReply(client->mDisconnectRunnable,
BluetoothValue(true),
EmptyString());
client->mDisconnectRunnable = nullptr;
}
}
void
BluetoothGattManager::SearchCompleteNotification(int aConnId, int aStatus)

View File

@ -12,6 +12,9 @@
#include "BluetoothProfileManagerBase.h"
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothReplyRunnable;
class BluetoothGattManager MOZ_FINAL : public nsIObserver
, public BluetoothGattNotificationHandler
{
@ -24,10 +27,25 @@ public:
static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
virtual ~BluetoothGattManager();
void Connect(const nsAString& aAppUuid,
const nsAString& aDeviceAddr,
BluetoothReplyRunnable* aRunnable);
void Disconnect(const nsAString& aAppUuid,
const nsAString& aDeviceAddr,
BluetoothReplyRunnable* aRunnable);
void UnregisterClient(int aClientIf,
BluetoothReplyRunnable* aRunnable);
private:
class CleanupResultHandler;
class CleanupResultHandlerRunnable;
class InitGattResultHandler;
class RegisterClientResultHandler;
class UnregisterClientResultHandler;
class ConnectResultHandler;
class DisconnectResultHandler;
BluetoothGattManager();

View File

@ -55,6 +55,15 @@
} \
} while(0)
#define ENSURE_GATT_MGR_IS_READY_VOID(gatt, runnable) \
do { \
if (!gatt) { \
NS_NAMED_LITERAL_STRING(replyError, "GattManager is not ready"); \
DispatchBluetoothReply(runnable, BluetoothValue(), replyError); \
return; \
} \
} while(0)
using namespace mozilla;
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
@ -1107,6 +1116,54 @@ BluetoothServiceBluedroid::ToggleCalls(BluetoothReplyRunnable* aRunnable)
{
}
//
// GATT Client
//
void
BluetoothServiceBluedroid::ConnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
BluetoothGattManager* gatt = BluetoothGattManager::Get();
ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
gatt->Connect(aAppUuid, aDeviceAddress, aRunnable);
}
void
BluetoothServiceBluedroid::DisconnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
BluetoothGattManager* gatt = BluetoothGattManager::Get();
ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
gatt->Disconnect(aAppUuid, aDeviceAddress, aRunnable);
}
void
BluetoothServiceBluedroid::UnregisterGattClientInternal(
int aClientIf, BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
BluetoothGattManager* gatt = BluetoothGattManager::Get();
ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
gatt->UnregisterClient(aClientIf, aRunnable);
}
//
// Bluetooth notifications
//

View File

@ -170,6 +170,24 @@ public:
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
//
// GATT Client
//
virtual void
ConnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
DisconnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
UnregisterGattClientInternal(int aClientIf,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
//
// Bluetooth notifications
//

View File

@ -4266,3 +4266,23 @@ BluetoothDBusService::UpdateNotification(ControlEventId aEventId,
Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
DispatchToDBusThread(task);
}
void
BluetoothDBusService::ConnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
}
void
BluetoothDBusService::DisconnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
}
void
BluetoothDBusService::UnregisterGattClientInternal(
int aClientIf, BluetoothReplyRunnable* aRunnable)
{
}

View File

@ -184,6 +184,20 @@ public:
virtual nsresult
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
virtual void
ConnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
DisconnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
UnregisterGattClientInternal(int aClientIf,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
private:
nsresult SendGetPropertyMessage(const nsAString& aPath,
const char* aInterface,

View File

@ -250,6 +250,12 @@ BluetoothParent::RecvPBluetoothRequestConstructor(
return actor->DoRequest(aRequest.get_SendMetaDataRequest());
case Request::TSendPlayStatusRequest:
return actor->DoRequest(aRequest.get_SendPlayStatusRequest());
case Request::TConnectGattClientRequest:
return actor->DoRequest(aRequest.get_ConnectGattClientRequest());
case Request::TDisconnectGattClientRequest:
return actor->DoRequest(aRequest.get_DisconnectGattClientRequest());
case Request::TUnregisterGattClientRequest:
return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
default:
MOZ_CRASH("Unknown type!");
}
@ -684,3 +690,41 @@ BluetoothRequestParent::DoRequest(const SendPlayStatusRequest& aRequest)
mReplyRunnable.get());
return true;
}
bool
BluetoothRequestParent::DoRequest(const ConnectGattClientRequest& aRequest)
{
MOZ_ASSERT(mService);
MOZ_ASSERT(mRequestType == Request::TConnectGattClientRequest);
mService->ConnectGattClientInternal(aRequest.appUuid(),
aRequest.deviceAddress(),
mReplyRunnable.get());
return true;
}
bool
BluetoothRequestParent::DoRequest(const DisconnectGattClientRequest& aRequest)
{
MOZ_ASSERT(mService);
MOZ_ASSERT(mRequestType == Request::TDisconnectGattClientRequest);
mService->DisconnectGattClientInternal(aRequest.appUuid(),
aRequest.deviceAddress(),
mReplyRunnable.get());
return true;
}
bool
BluetoothRequestParent::DoRequest(const UnregisterGattClientRequest& aRequest)
{
MOZ_ASSERT(mService);
MOZ_ASSERT(mRequestType == Request::TUnregisterGattClientRequest);
mService->UnregisterGattClientInternal(aRequest.clientIf(),
mReplyRunnable.get());
return true;
}

View File

@ -216,6 +216,15 @@ protected:
bool
DoRequest(const SendPlayStatusRequest& aRequest);
bool
DoRequest(const ConnectGattClientRequest& aRequest);
bool
DoRequest(const DisconnectGattClientRequest& aRequest);
bool
DoRequest(const UnregisterGattClientRequest& aRequest);
};
END_BLUETOOTH_NAMESPACE

View File

@ -141,7 +141,7 @@ BluetoothServiceChildProcess::GetPairedDevicePropertiesInternal(
nsresult
BluetoothServiceChildProcess::FetchUuidsInternal(
const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
return NS_OK;
@ -379,6 +379,31 @@ BluetoothServiceChildProcess::SendPlayStatus(int64_t aDuration,
nsString(aPlayStatus)));
}
void
BluetoothServiceChildProcess::ConnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, ConnectGattClientRequest(nsString(aAppUuid),
nsString(aDeviceAddress)));
}
void
BluetoothServiceChildProcess::DisconnectGattClientInternal(
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable,
DisconnectGattClientRequest(nsString(aAppUuid), nsString(aDeviceAddress)));
}
void
BluetoothServiceChildProcess::UnregisterGattClientInternal(
int aClientIf, BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, UnregisterGattClientRequest(aClientIf));
}
nsresult
BluetoothServiceChildProcess::HandleStartup()
{

View File

@ -192,6 +192,20 @@ public:
SendInputMessage(const nsAString& aDeviceAddresses,
const nsAString& aMessage) MOZ_OVERRIDE;
virtual void
ConnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
DisconnectGattClientInternal(const nsAString& aAppUuid,
const nsAString& aDeviceAddress,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual void
UnregisterGattClientInternal(int aClientIf,
BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
protected:
BluetoothServiceChildProcess();
virtual ~BluetoothServiceChildProcess();

View File

@ -176,6 +176,23 @@ struct SendPlayStatusRequest
nsString playStatus;
};
struct ConnectGattClientRequest
{
nsString appUuid;
nsString deviceAddress;
};
struct DisconnectGattClientRequest
{
nsString appUuid;
nsString deviceAddress;
};
struct UnregisterGattClientRequest
{
int clientIf;
};
union Request
{
GetAdaptersRequest;
@ -208,6 +225,9 @@ union Request
ToggleCallsRequest;
SendMetaDataRequest;
SendPlayStatusRequest;
ConnectGattClientRequest;
DisconnectGattClientRequest;
UnregisterGattClientRequest;
};
protocol PBluetooth

View File

@ -10,6 +10,7 @@ if CONFIG['MOZ_B2G_BT']:
'BluetoothClassOfDevice.cpp',
'BluetoothDevice.cpp',
'BluetoothDiscoveryHandle.cpp',
'BluetoothGatt.cpp',
'BluetoothHidManager.cpp',
'BluetoothInterface.cpp',
'BluetoothManager.cpp',
@ -122,6 +123,7 @@ EXPORTS.mozilla.dom.bluetooth += [
'BluetoothCommon.h',
'BluetoothDevice.h',
'BluetoothDiscoveryHandle.h',
'BluetoothGatt.h',
'BluetoothManager.h',
'BluetoothPairingHandle.h',
'BluetoothPairingListener.h',

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Mozilla Foundation
* Copyright (C) 2012-2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -40,14 +40,15 @@ DEF_GONK_RECORDER_PROFILE(CAMCORDER_QUALITY_1080P, "1080p")
* profiles may have more than one resolution, depending on the camera.
*/
DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 864, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 854, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 800, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 768, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("vga", 640, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("hvga", 480, 320)
DEF_GONK_RECORDER_PROFILE_DETECT("wqvga", 400, 240)
DEF_GONK_RECORDER_PROFILE_DETECT("qvga", 320, 240)
DEF_GONK_RECORDER_PROFILE_DETECT("4kuhd", 3840, 2160)
DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 864, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 854, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 800, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("wvga", 768, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("vga", 640, 480)
DEF_GONK_RECORDER_PROFILE_DETECT("hvga", 480, 320)
DEF_GONK_RECORDER_PROFILE_DETECT("wqvga", 400, 240)
DEF_GONK_RECORDER_PROFILE_DETECT("qvga", 320, 240)
#undef DEF_GONK_RECORDER_PROFILE
#undef DEF_GONK_RECORDER_PROFILE_DETECT

View File

@ -600,7 +600,7 @@ bool OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aTimeUs,
// For some codecs, the length of first decoded frame after seek is 0.
// Need to ignore it and continue to find the next one
if (mVideoBuffer->range_length() == 0) {
ReleaseVideoBuffer();
PostReleaseVideoBuffer(mVideoBuffer, FenceHandle());
findNextBuffer = true;
}
}

View File

@ -178,8 +178,10 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
},
setFocusApp: function setFocusApp(id, isFocus) {
// if calling setNFCFocus(true) on the same browser-element, ignore.
if (isFocus && (id == this.focusApp)) {
// if calling setNFCFocus(true) on the browser-element which is already
// focused, or calling setNFCFocus(false) on the browser-element which has
// lost focus already, ignore.
if (isFocus == (id == this.focusApp)) {
return;
}

View File

@ -314,7 +314,7 @@ typedef enum {
AUDIO_DEVICE_IN_DEFAULT),
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
} audio_devices_t;
#else
#elif ANDROID_VERSION < 21
enum {
AUDIO_DEVICE_NONE = 0x0,
/* reserved bits */
@ -415,6 +415,133 @@ enum {
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
};
typedef uint32_t audio_devices_t;
#else
enum {
AUDIO_DEVICE_NONE = 0x0,
/* reserved bits */
AUDIO_DEVICE_BIT_IN = 0x80000000,
AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
/* output devices */
AUDIO_DEVICE_OUT_EARPIECE = 0x1,
AUDIO_DEVICE_OUT_SPEAKER = 0x2,
AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10,
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400,
AUDIO_DEVICE_OUT_HDMI = AUDIO_DEVICE_OUT_AUX_DIGITAL,
/* uses an analog connection (multiplexed over the USB connector pins for instance) */
AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
/* USB accessory mode: your Android device is a USB device and the dock is a USB host */
AUDIO_DEVICE_OUT_USB_ACCESSORY = 0x2000,
/* USB host mode: your Android device is a USB host and the dock is a USB device */
AUDIO_DEVICE_OUT_USB_DEVICE = 0x4000,
AUDIO_DEVICE_OUT_REMOTE_SUBMIX = 0x8000,
/* Telephony voice TX path */
AUDIO_DEVICE_OUT_TELEPHONY_TX = 0x10000,
/* Analog jack with line impedance detected */
AUDIO_DEVICE_OUT_LINE = 0x20000,
/* HDMI Audio Return Channel */
AUDIO_DEVICE_OUT_HDMI_ARC = 0x40000,
/* S/PDIF out */
AUDIO_DEVICE_OUT_SPDIF = 0x80000,
/* FM transmitter out */
AUDIO_DEVICE_OUT_FM = 0x100000,
/* Line out for av devices */
AUDIO_DEVICE_OUT_AUX_LINE = 0x200000,
/* limited-output speaker device for acoustic safety */
AUDIO_DEVICE_OUT_SPEAKER_SAFE = 0x400000,
AUDIO_DEVICE_OUT_DEFAULT = AUDIO_DEVICE_BIT_DEFAULT,
AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE |
AUDIO_DEVICE_OUT_SPEAKER |
AUDIO_DEVICE_OUT_WIRED_HEADSET |
AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
AUDIO_DEVICE_OUT_AUX_DIGITAL |
AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
AUDIO_DEVICE_OUT_USB_ACCESSORY |
AUDIO_DEVICE_OUT_USB_DEVICE |
AUDIO_DEVICE_OUT_REMOTE_SUBMIX |
AUDIO_DEVICE_OUT_TELEPHONY_TX |
AUDIO_DEVICE_OUT_LINE |
AUDIO_DEVICE_OUT_HDMI_ARC |
AUDIO_DEVICE_OUT_SPDIF |
AUDIO_DEVICE_OUT_FM |
AUDIO_DEVICE_OUT_AUX_LINE |
AUDIO_DEVICE_OUT_SPEAKER_SAFE |
AUDIO_DEVICE_OUT_DEFAULT),
AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
AUDIO_DEVICE_OUT_ALL_USB = (AUDIO_DEVICE_OUT_USB_ACCESSORY |
AUDIO_DEVICE_OUT_USB_DEVICE),
/* input devices */
AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1,
AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2,
AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4,
AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8,
AUDIO_DEVICE_IN_WIRED_HEADSET = AUDIO_DEVICE_BIT_IN | 0x10,
AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20,
AUDIO_DEVICE_IN_HDMI = AUDIO_DEVICE_IN_AUX_DIGITAL,
/* Telephony voice RX path */
AUDIO_DEVICE_IN_VOICE_CALL = AUDIO_DEVICE_BIT_IN | 0x40,
AUDIO_DEVICE_IN_BACK_MIC = AUDIO_DEVICE_BIT_IN | 0x80,
AUDIO_DEVICE_IN_REMOTE_SUBMIX = AUDIO_DEVICE_BIT_IN | 0x100,
AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x200,
AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x400,
AUDIO_DEVICE_IN_USB_ACCESSORY = AUDIO_DEVICE_BIT_IN | 0x800,
AUDIO_DEVICE_IN_USB_DEVICE = AUDIO_DEVICE_BIT_IN | 0x1000,
/* FM tuner input */
AUDIO_DEVICE_IN_FM_TUNER = AUDIO_DEVICE_BIT_IN | 0x2000,
/* TV tuner input */
AUDIO_DEVICE_IN_TV_TUNER = AUDIO_DEVICE_BIT_IN | 0x4000,
/* Analog jack with line impedance detected */
AUDIO_DEVICE_IN_LINE = AUDIO_DEVICE_BIT_IN | 0x8000,
/* S/PDIF in */
AUDIO_DEVICE_IN_SPDIF = AUDIO_DEVICE_BIT_IN | 0x10000,
AUDIO_DEVICE_IN_BLUETOOTH_A2DP = AUDIO_DEVICE_BIT_IN | 0x20000,
AUDIO_DEVICE_IN_LOOPBACK = AUDIO_DEVICE_BIT_IN | 0x40000,
AUDIO_DEVICE_IN_DEFAULT = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT,
AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
AUDIO_DEVICE_IN_AMBIENT |
AUDIO_DEVICE_IN_BUILTIN_MIC |
AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
AUDIO_DEVICE_IN_WIRED_HEADSET |
AUDIO_DEVICE_IN_AUX_DIGITAL |
AUDIO_DEVICE_IN_VOICE_CALL |
AUDIO_DEVICE_IN_BACK_MIC |
AUDIO_DEVICE_IN_REMOTE_SUBMIX |
AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
AUDIO_DEVICE_IN_USB_ACCESSORY |
AUDIO_DEVICE_IN_USB_DEVICE |
AUDIO_DEVICE_IN_FM_TUNER |
AUDIO_DEVICE_IN_TV_TUNER |
AUDIO_DEVICE_IN_LINE |
AUDIO_DEVICE_IN_SPDIF |
AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
AUDIO_DEVICE_IN_LOOPBACK |
AUDIO_DEVICE_IN_DEFAULT),
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY |
AUDIO_DEVICE_IN_USB_DEVICE),
};
typedef uint32_t audio_devices_t;
#endif

View File

@ -465,6 +465,8 @@ this.NETWORK_CREG_TECH_EHRPD = 13;
this.NETWORK_CREG_TECH_LTE = 14;
this.NETWORK_CREG_TECH_HSPAP = 15;
this.NETWORK_CREG_TECH_GSM = 16;
this.NETWORK_CREG_TECH_DCHSPAP_1 = 18; // Some devices reports as 18
this.NETWORK_CREG_TECH_DCHSPAP_2 = 19; // Some others report it as 19
this.CELL_INFO_TYPE_GSM = 1;
this.CELL_INFO_TYPE_CDMA = 2;
@ -2913,7 +2915,10 @@ this.GECKO_RADIO_TECH = [
"ehrpd",
"lte",
"hspa+",
"gsm"
"gsm",
null,
"hspa+", // DC-HSPA+
"hspa+"
];
this.GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN = -1;

View File

@ -4226,6 +4226,8 @@ RilObject.prototype = {
case NETWORK_CREG_TECH_LTE:
case NETWORK_CREG_TECH_HSPAP:
case NETWORK_CREG_TECH_GSM:
case NETWORK_CREG_TECH_DCHSPAP_1:
case NETWORK_CREG_TECH_DCHSPAP_2:
return true;
}
@ -6194,6 +6196,8 @@ RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = function REQUEST_GET_NEI
case NETWORK_CREG_TECH_HSUPA:
case NETWORK_CREG_TECH_HSPA:
case NETWORK_CREG_TECH_HSPAP:
case NETWORK_CREG_TECH_DCHSPAP_1:
case NETWORK_CREG_TECH_DCHSPAP_2:
cellId.wcdmaPsc = this.parseInt(cid, -1, 16);
break;
}

View File

@ -13,6 +13,12 @@ interface BluetoothDevice : EventTarget
readonly attribute boolean paired;
readonly attribute BluetoothDeviceType type;
/**
* Retrieve the BluetoothGatt interface to interact with remote BLE devices.
* This attribute is null if the device type is not dual or le.
*/
readonly attribute BluetoothGatt? gatt;
[Cached, Pure]
readonly attribute sequence<DOMString> uuids;

View File

@ -0,0 +1,38 @@
/* -*- 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/. */
[CheckPermissions="bluetooth"]
interface BluetoothGatt : EventTarget
{
readonly attribute BluetoothConnectionState connectionState;
// Fired when attribute connectionState changed
attribute EventHandler onconnectionstatechanged;
/**
* Connect/Disconnect to the remote BLE device if the connectionState is
* disconnected/connected. Otherwise, the Promise will be rejected directly.
*
* If current connectionState is disconnected/connected,
* 1) connectionState change to connecting/disconnecting along with a
* connectionstatechanged event.
* 2) connectionState change to connected/disconnected if the operation
* succeeds. Otherwise, change to disconnected/connected.
* 3) Promise is resolved or rejected according to the operation result.
*/
[NewObject]
Promise<void> connect();
[NewObject]
Promise<void> disconnect();
};
enum BluetoothConnectionState
{
"disconnected",
"disconnecting",
"connected",
"connecting"
};

View File

@ -632,6 +632,7 @@ if CONFIG['MOZ_B2G_BT']:
'BluetoothClassOfDevice.webidl',
'BluetoothDevice2.webidl',
'BluetoothDiscoveryHandle.webidl',
'BluetoothGatt.webidl',
'BluetoothManager2.webidl',
'BluetoothPairingHandle.webidl',
'BluetoothPairingListener.webidl',

View File

@ -37,6 +37,7 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop spec
[test_bug639338.xhtml]
[test_bug790265.xhtml]
[test_bug821850.html]
skip-if = buildapp == 'mulet'
[test_bug844783.html]
[test_bug872273.xhtml]
[test_bug946815.html]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -9,6 +9,6 @@ support-files =
[test_private_window_from_content.html]
# Next two tests are disabled in e10s because of bug 989501.
[test_window_open_position_constraint.html]
skip-if = toolkit == 'android' || e10s
skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'
[test_window_open_units.html]
skip-if = toolkit == 'android' || e10s
skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'

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