mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Merge m-c to inbound.
This commit is contained in:
commit
4829857b56
2
CLOBBER
2
CLOBBER
@ -18,4 +18,4 @@
|
||||
# Modifying this file will now automatically clobber the buildbot machines \o/
|
||||
#
|
||||
|
||||
Another Windows WebIDL clobber needed due to bug 928195
|
||||
More Windows webidl changes
|
||||
|
@ -439,6 +439,11 @@ pref("dom.mozNetworkStats.enabled", true);
|
||||
pref("dom.webapps.firstRunWithSIM", true);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_B2G_RIL
|
||||
// SingleVariant
|
||||
pref("dom.mozApps.single_variant_sourcedir", "/persist/svoperapps");
|
||||
#endif
|
||||
|
||||
// WebSettings
|
||||
pref("dom.mozSettings.enabled", true);
|
||||
pref("dom.navigator-property.disable.mozSettings", false);
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "cf23b263b4bbf1024210350f24c7e6eb5872a4be",
|
||||
"revision": "c2506884aba22b968d3ab1ad6c28f0782b22a17f",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -518,9 +518,9 @@
|
||||
<menuitem id="menu_devAppMgr"
|
||||
observes="devtoolsMenuBroadcaster_DevAppMgr"
|
||||
accesskey="&devAppMgrMenu.accesskey;"/>
|
||||
<menuitem id="menu_chromeDebugger"
|
||||
observes="devtoolsMenuBroadcaster_ChromeDebugger"
|
||||
accesskey="&chromeDebuggerMenu.accesskey;"/>
|
||||
<menuitem id="menu_browserToolbox"
|
||||
observes="devtoolsMenuBroadcaster_BrowserToolbox"
|
||||
accesskey="&browserToolboxMenu.accesskey;"/>
|
||||
<menuitem id="menu_browserConsole"
|
||||
observes="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
accesskey="&browserConsoleCmd.accesskey;"/>
|
||||
|
@ -95,7 +95,7 @@
|
||||
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
|
||||
<command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
|
||||
<command id="Tools:ChromeDebugger" oncommand="BrowserDebuggerProcess.init();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserToolbox" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserConsole" oncommand="HUDService.toggleBrowserConsole();"/>
|
||||
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
|
||||
<command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
|
||||
@ -185,9 +185,9 @@
|
||||
<broadcaster id="devtoolsMenuBroadcaster_DevAppMgr"
|
||||
label="&devAppMgrMenu.label;"
|
||||
command="Tools:DevAppMgr"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
|
||||
label="&chromeDebuggerMenu.label;"
|
||||
command="Tools:ChromeDebugger"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserToolbox"
|
||||
label="&browserToolboxMenu.label;"
|
||||
command="Tools:BrowserToolbox"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
label="&browserConsoleCmd.label;"
|
||||
key="key_browserConsole"
|
||||
|
@ -117,10 +117,10 @@ XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
|
||||
return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "BrowserDebuggerProcess", function() {
|
||||
XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", tmp);
|
||||
return tmp.BrowserDebuggerProcess;
|
||||
Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", tmp);
|
||||
return tmp.BrowserToolboxProcess;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Social",
|
||||
|
@ -134,17 +134,19 @@ var tests = {
|
||||
let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
|
||||
domwindow.addEventListener("unload", function _unload() {
|
||||
domwindow.removeEventListener("unload", _unload, false);
|
||||
windowWasClosed = true;
|
||||
}, false);
|
||||
info("dialog opened, waiting for focus");
|
||||
waitForFocus(function() {
|
||||
domwindow.addEventListener("load", function _load() {
|
||||
domwindow.removeEventListener("load", _load, false);
|
||||
|
||||
domwindow.addEventListener("unload", function _unload() {
|
||||
domwindow.removeEventListener("unload", _unload, false);
|
||||
info("blocklist window was closed");
|
||||
windowWasClosed = true;
|
||||
}, false);
|
||||
|
||||
is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
|
||||
executeSoon(function() {
|
||||
domwindow.close();
|
||||
});
|
||||
}, domwindow);
|
||||
domwindow.close();
|
||||
|
||||
}, false);
|
||||
},
|
||||
onCloseWindow: function(aXULWindow) { },
|
||||
onWindowTitleChange: function(aXULWindow, aNewTitle) { }
|
||||
|
@ -157,8 +157,7 @@ let CustomizableUIInternal = {
|
||||
} catch (ex) { }
|
||||
|
||||
if (isMetroCapable) {
|
||||
// TODO: Bug 942915 - Place 'Metro Mode' button as a default
|
||||
// for Windows 8 in the customization panel.
|
||||
panelPlacements.push("switch-to-metro-button");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -28,7 +28,7 @@ let gTests = [
|
||||
desc: "Creating and destroying a widget should correctly deal with panel placeholders",
|
||||
run: function() {
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
is(panel.querySelectorAll(".panel-customization-placeholder").length, 3, "The number of placeholders should be correct.");
|
||||
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 2 : 3, "The number of placeholders should be correct.");
|
||||
CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
|
||||
let elem = document.getElementById(kTestWidget2);
|
||||
let wrapper = document.getElementById("wrapper-" + kTestWidget2);
|
||||
@ -36,7 +36,7 @@ let gTests = [
|
||||
ok(wrapper, "There should be a wrapper");
|
||||
is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
|
||||
is(wrapper.parentNode, panel, "Wrapper should be in panel");
|
||||
is(panel.querySelectorAll(".panel-customization-placeholder").length, 2, "The number of placeholders should be correct.");
|
||||
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 1 : 2, "The number of placeholders should be correct.");
|
||||
CustomizableUI.destroyWidget(kTestWidget2);
|
||||
wrapper = document.getElementById("wrapper-" + kTestWidget2);
|
||||
ok(!wrapper, "There should be a wrapper");
|
||||
|
@ -21,6 +21,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(zoomControls, printButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
@ -47,6 +48,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(zoomControls, savePageButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state.");
|
||||
@ -70,6 +72,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(zoomControls, newWindowButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
|
||||
@ -94,6 +97,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(zoomControls, historyPanelMenu);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
@ -121,6 +125,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(zoomControls, preferencesButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
@ -148,6 +153,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterInsert);
|
||||
simulateItemDrag(developerButton, zoomControls);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
@ -186,6 +192,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterInsert);
|
||||
simulateItemDrag(developerButton, editControls);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
@ -221,6 +228,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, zoomControls);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
|
||||
@ -244,6 +252,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, newWindowButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
@ -270,6 +279,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, privateBrowsingButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
@ -296,6 +306,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, savePageButton);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
@ -321,6 +332,7 @@ let gTests = [
|
||||
"preferences-button",
|
||||
"add-ons-button",
|
||||
"edit-controls"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, panel);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
@ -345,6 +357,7 @@ let gTests = [
|
||||
"find-button",
|
||||
"preferences-button",
|
||||
"add-ons-button"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
let paletteChildElementCount = palette.childElementCount;
|
||||
simulateItemDrag(editControls, palette);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
@ -368,7 +381,8 @@ let gTests = [
|
||||
run: function() {
|
||||
let editControls = document.getElementById("edit-controls");
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let numPlaceholders = isInWin8() ? 2 : 3;
|
||||
for (let i = 0; i < numPlaceholders; i++) {
|
||||
// NB: We can't just iterate over all of the placeholders
|
||||
// because each drag-drop action recreates them.
|
||||
let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i];
|
||||
@ -383,6 +397,7 @@ let gTests = [
|
||||
"preferences-button",
|
||||
"add-ons-button",
|
||||
"edit-controls"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, placeholder);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
@ -425,11 +440,16 @@ let gTests = [
|
||||
"preferences-button",
|
||||
"add-ons-button",
|
||||
"edit-controls"];
|
||||
addSwitchToMetroButtonInWindows8(placementsAfterMove);
|
||||
simulateItemDrag(editControls, target);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
let itemToDrag = "sync-button";
|
||||
let button = document.getElementById(itemToDrag);
|
||||
placementsAfterMove.push(itemToDrag);
|
||||
if (!isInWin8()) {
|
||||
placementsAfterMove.push(itemToDrag);
|
||||
} else {
|
||||
placementsAfterMove.splice(11, 0, itemToDrag);
|
||||
}
|
||||
simulateItemDrag(button, editControls);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
|
||||
|
||||
|
@ -11,18 +11,25 @@ let gTests = [
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
|
||||
|
||||
let placementsAfterAppend = placements.concat(["developer-button"]);
|
||||
simulateItemDrag(btn, panel);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
if (!isInWin8()) {
|
||||
placements = placements.concat(["developer-button"]);
|
||||
simulateItemDrag(btn, panel);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
} else {
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state.");
|
||||
}
|
||||
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
|
||||
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting");
|
||||
|
||||
yield endCustomizing();
|
||||
yield startCustomizing();
|
||||
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
|
||||
|
||||
let palette = document.getElementById("customization-palette");
|
||||
simulateItemDrag(btn, palette);
|
||||
if (!isInWin8()) {
|
||||
let palette = document.getElementById("customization-palette");
|
||||
simulateItemDrag(btn, palette);
|
||||
}
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
|
||||
},
|
||||
},
|
||||
@ -34,10 +41,15 @@ let gTests = [
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
|
||||
|
||||
let placementsAfterAppend = placements.concat(["developer-button", "sync-button"]);
|
||||
simulateItemDrag(btn, panel);
|
||||
btn = document.getElementById("sync-button");
|
||||
let placementsAfterAppend = placements.concat(["developer-button"]);
|
||||
simulateItemDrag(btn, panel);
|
||||
|
||||
if (!isInWin8()) {
|
||||
placementsAfterAppend = placementsAfterAppend.concat(["sync-button"]);
|
||||
btn = document.getElementById("sync-button");
|
||||
simulateItemDrag(btn, panel);
|
||||
}
|
||||
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
|
||||
@ -48,8 +60,11 @@ let gTests = [
|
||||
|
||||
let palette = document.getElementById("customization-palette");
|
||||
simulateItemDrag(btn, palette);
|
||||
btn = document.getElementById("developer-button");
|
||||
simulateItemDrag(btn, palette);
|
||||
|
||||
if (!isInWin8()) {
|
||||
btn = document.getElementById("developer-button");
|
||||
simulateItemDrag(btn, palette);
|
||||
}
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
|
||||
},
|
||||
},
|
||||
@ -58,12 +73,19 @@ let gTests = [
|
||||
setup: startCustomizing,
|
||||
run: function() {
|
||||
let btn = document.getElementById("add-ons-button");
|
||||
let btn2 = document.getElementById("switch-to-metro-button");
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
let palette = document.getElementById("customization-palette");
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
|
||||
|
||||
let placementsAfterAppend = placements.filter(p => p != btn.id);
|
||||
simulateItemDrag(btn, palette);
|
||||
|
||||
if (isInWin8()) {
|
||||
placementsAfterAppend = placementsAfterAppend.filter(p => p != btn2.id);
|
||||
simulateItemDrag(btn2, palette);
|
||||
}
|
||||
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
|
||||
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
|
||||
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
|
||||
@ -73,6 +95,11 @@ let gTests = [
|
||||
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
|
||||
|
||||
simulateItemDrag(btn, panel);
|
||||
|
||||
if (isInWin8()) {
|
||||
simulateItemDrag(btn2, panel);
|
||||
}
|
||||
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
|
||||
},
|
||||
@ -82,9 +109,17 @@ let gTests = [
|
||||
setup: startCustomizing,
|
||||
run: function() {
|
||||
let btn = document.getElementById("edit-controls");
|
||||
let metroBtn = document.getElementById("switch-to-metro-button");
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
let palette = document.getElementById("customization-palette");
|
||||
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
|
||||
|
||||
if (isInWin8()) {
|
||||
// Remove switch-to-metro-button
|
||||
placements.pop();
|
||||
simulateItemDrag(metroBtn, palette);
|
||||
}
|
||||
|
||||
let placementsAfterAppend = placements.concat([placements.shift()]);
|
||||
simulateItemDrag(btn, panel);
|
||||
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
|
||||
@ -95,22 +130,26 @@ let gTests = [
|
||||
yield startCustomizing();
|
||||
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
|
||||
|
||||
if (isInWin8()) {
|
||||
simulateItemDrag(metroBtn, panel);
|
||||
}
|
||||
let zoomControls = document.getElementById("zoom-controls");
|
||||
simulateItemDrag(btn, zoomControls);
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "The default placements should have three placeholders at the bottom.",
|
||||
desc: "The default placements should have three placeholders at the bottom (or 2 in win8).",
|
||||
setup: startCustomizing,
|
||||
run: function() {
|
||||
let numPlaceholders = isInWin8() ? 2 : 3;
|
||||
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state.");
|
||||
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
|
||||
is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting");
|
||||
|
||||
yield endCustomizing();
|
||||
yield startCustomizing();
|
||||
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
|
||||
is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering");
|
||||
|
||||
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
|
||||
},
|
||||
|
@ -54,6 +54,21 @@ function resetCustomization() {
|
||||
return CustomizableUI.reset();
|
||||
}
|
||||
|
||||
function isInWin8() {
|
||||
let sysInfo = Services.sysinfo;
|
||||
let osName = sysInfo.getProperty("name");
|
||||
let version = sysInfo.getProperty("version");
|
||||
|
||||
// Windows 8 is version >= 6.2
|
||||
return osName == "Windows_NT" && version >= 6.2;
|
||||
}
|
||||
|
||||
function addSwitchToMetroButtonInWindows8(areaPanelPlacements) {
|
||||
if (isInWin8()) {
|
||||
areaPanelPlacements.push("switch-to-metro-button");
|
||||
}
|
||||
}
|
||||
|
||||
function assertAreaPlacements(areaId, expectedPlacements) {
|
||||
let actualPlacements = getAreaWidgetIds(areaId);
|
||||
is(actualPlacements.length, expectedPlacements.length,
|
||||
|
@ -1454,8 +1454,12 @@ let SessionStoreInternal = {
|
||||
var state = JSON.parse(aState);
|
||||
}
|
||||
catch (ex) { /* invalid state object - don't restore anything */ }
|
||||
if (!state || !state.windows)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!state) {
|
||||
throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!state.windows) {
|
||||
throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
this._browserSetState = true;
|
||||
|
||||
@ -1501,19 +1505,24 @@ let SessionStoreInternal = {
|
||||
return this._toJSONString({ windows: [data] });
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
},
|
||||
|
||||
setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
|
||||
if (!aWindow.__SSi)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aWindow.__SSi) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
|
||||
},
|
||||
|
||||
getTabState: function ssi_getTabState(aTab) {
|
||||
if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aTab.ownerDocument) {
|
||||
throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!aTab.ownerDocument.defaultView.__SSi) {
|
||||
throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
let tabState = TabState.collectSync(aTab);
|
||||
|
||||
@ -1527,26 +1536,21 @@ let SessionStoreInternal = {
|
||||
// by |restoreTabs|.
|
||||
let tabState = JSON.parse(aState);
|
||||
if (!tabState) {
|
||||
debug("Empty state argument");
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (typeof tabState != "object") {
|
||||
debug("State argument does not represent an object");
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!("entries" in tabState)) {
|
||||
debug("State argument must contain field 'entries'");
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!aTab.ownerDocument) {
|
||||
debug("Tab argument must have an owner document");
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
if (!("__SSi" in window)) {
|
||||
debug("Default view of ownerDocument must have a unique identifier");
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
if (aTab.linkedBrowser.__SS_restoreState) {
|
||||
@ -1559,9 +1563,15 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
|
||||
if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
|
||||
!aWindow.getBrowser)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aTab.ownerDocument) {
|
||||
throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!aTab.ownerDocument.defaultView.__SSi) {
|
||||
throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
if (!aWindow.getBrowser) {
|
||||
throw Components.Exception("Invalid window object: no getBrowser", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// Flush all data queued in the content script because we will need that
|
||||
// state to properly duplicate the given tab.
|
||||
@ -1586,31 +1596,32 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
setNumberOfTabsClosedLast: function ssi_setNumberOfTabsClosedLast(aWindow, aNumber) {
|
||||
if (this._disabledForMultiProcess)
|
||||
if (this._disabledForMultiProcess) {
|
||||
return;
|
||||
|
||||
if ("__SSi" in aWindow) {
|
||||
return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!("__SSi" in aWindow)) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
|
||||
},
|
||||
|
||||
/* Used to undo batch tab-close operations. Defaults to 1. */
|
||||
getNumberOfTabsClosedLast: function ssi_getNumberOfTabsClosedLast(aWindow) {
|
||||
if (this._disabledForMultiProcess)
|
||||
if (this._disabledForMultiProcess) {
|
||||
return 0;
|
||||
|
||||
if ("__SSi" in aWindow) {
|
||||
// Blank tabs cannot be undo-closed, so the number returned by
|
||||
// the NumberOfTabsClosedLastPerWindow can be greater than the
|
||||
// return value of getClosedTabCount. We won't restore blank
|
||||
// tabs, so we return the minimum of these two values.
|
||||
return Math.min(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
|
||||
this.getClosedTabCount(aWindow));
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!("__SSi" in aWindow)) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
// Blank tabs cannot be undo-closed, so the number returned by
|
||||
// the NumberOfTabsClosedLastPerWindow can be greater than the
|
||||
// return value of getClosedTabCount. We won't restore blank
|
||||
// tabs, so we return the minimum of these two values.
|
||||
return Math.min(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
|
||||
this.getClosedTabCount(aWindow));
|
||||
},
|
||||
|
||||
getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
|
||||
@ -1618,11 +1629,11 @@ let SessionStoreInternal = {
|
||||
return this._windows[aWindow.__SSi]._closedTabs.length;
|
||||
}
|
||||
|
||||
if (DyingWindowCache.has(aWindow)) {
|
||||
return DyingWindowCache.get(aWindow)._closedTabs.length;
|
||||
if (!DyingWindowCache.has(aWindow)) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
return DyingWindowCache.get(aWindow)._closedTabs.length;
|
||||
},
|
||||
|
||||
getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
|
||||
@ -1630,24 +1641,26 @@ let SessionStoreInternal = {
|
||||
return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
|
||||
}
|
||||
|
||||
if (DyingWindowCache.has(aWindow)) {
|
||||
let data = DyingWindowCache.get(aWindow);
|
||||
return this._toJSONString(data._closedTabs);
|
||||
if (!DyingWindowCache.has(aWindow)) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
let data = DyingWindowCache.get(aWindow);
|
||||
return this._toJSONString(data._closedTabs);
|
||||
},
|
||||
|
||||
undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
|
||||
if (!aWindow.__SSi)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aWindow.__SSi) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
|
||||
|
||||
// default to the most-recently closed tab
|
||||
aIndex = aIndex || 0;
|
||||
if (!(aIndex in closedTabs))
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!(aIndex in closedTabs)) {
|
||||
throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// fetch the data of closed tab, while removing it from the array
|
||||
let closedTab = closedTabs.splice(aIndex, 1).shift();
|
||||
@ -1671,15 +1684,17 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
|
||||
if (!aWindow.__SSi)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!aWindow.__SSi) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
|
||||
|
||||
// default to the most-recently closed tab
|
||||
aIndex = aIndex || 0;
|
||||
if (!(aIndex in closedTabs))
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!(aIndex in closedTabs)) {
|
||||
throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// remove closed tab from the array
|
||||
closedTabs.splice(aIndex, 1);
|
||||
@ -1694,8 +1709,9 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
|
||||
if (!(aIndex in this._closedWindows))
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!(aIndex in this._closedWindows)) {
|
||||
throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// reopen the window
|
||||
let state = { windows: this._closedWindows.splice(aIndex, 1) };
|
||||
@ -1707,16 +1723,18 @@ let SessionStoreInternal = {
|
||||
forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
|
||||
// default to the most-recently closed window
|
||||
aIndex = aIndex || 0;
|
||||
if (!(aIndex in this._closedWindows))
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!(aIndex in this._closedWindows)) {
|
||||
throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
// remove closed window from the array
|
||||
this._closedWindows.splice(aIndex, 1);
|
||||
},
|
||||
|
||||
getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
|
||||
if (this._disabledForMultiProcess)
|
||||
if (this._disabledForMultiProcess) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if ("__SSi" in aWindow) {
|
||||
var data = this._windows[aWindow.__SSi].extData || {};
|
||||
@ -1728,20 +1746,18 @@ let SessionStoreInternal = {
|
||||
return data[aKey] || "";
|
||||
}
|
||||
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
},
|
||||
|
||||
setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
|
||||
if (aWindow.__SSi) {
|
||||
if (!this._windows[aWindow.__SSi].extData) {
|
||||
this._windows[aWindow.__SSi].extData = {};
|
||||
}
|
||||
this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
|
||||
this.saveStateDelayed(aWindow);
|
||||
if (!("__SSi" in aWindow)) {
|
||||
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
else {
|
||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||
if (!this._windows[aWindow.__SSi].extData) {
|
||||
this._windows[aWindow.__SSi].extData = {};
|
||||
}
|
||||
this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
|
||||
this.saveStateDelayed(aWindow);
|
||||
},
|
||||
|
||||
deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
|
||||
@ -1840,8 +1856,9 @@ let SessionStoreInternal = {
|
||||
*/
|
||||
restoreLastSession: function ssi_restoreLastSession() {
|
||||
// Use the public getter since it also checks PB mode
|
||||
if (!this.canRestoreLastSession)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
|
||||
if (!this.canRestoreLastSession) {
|
||||
throw Components.Exception("Last session can not be restored");
|
||||
}
|
||||
|
||||
// First collect each window with its id...
|
||||
let windows = {};
|
||||
@ -1853,8 +1870,9 @@ let SessionStoreInternal = {
|
||||
let lastSessionState = LastSession.getState();
|
||||
|
||||
// This shouldn't ever be the case...
|
||||
if (!lastSessionState.windows.length)
|
||||
throw (Components.returnCode = Cr.NS_ERROR_UNEXPECTED);
|
||||
if (!lastSessionState.windows.length) {
|
||||
throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
|
||||
// We're technically doing a restore, so set things up so we send the
|
||||
// notification when we're done. We want to send "sessionstore-browser-state-restored".
|
||||
|
@ -67,6 +67,9 @@ let UI = {
|
||||
case "toolbox-close":
|
||||
this.closeToolboxTab(json.uid);
|
||||
break;
|
||||
case "toolbox-title":
|
||||
// Not implemented
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unknown message: " + json.name);
|
||||
}
|
||||
|
@ -44,26 +44,15 @@ function test() {
|
||||
let extraPools = conn._extraPools;
|
||||
let globalPool;
|
||||
|
||||
for (let pool of extraPools) {
|
||||
if (Object.keys(pool._actors).some(e => {
|
||||
// Tab actors are in the global pool.
|
||||
let re = new RegExp(conn._prefix + "tab", "g");
|
||||
return e.match(re) !== null;
|
||||
})) {
|
||||
globalPool = pool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Then we look if the global pool contains only one test actor.
|
||||
let actorPrefix = conn._prefix + "test_one";
|
||||
let actors = Object.keys(globalPool._actors).join();
|
||||
info("Global actors: " + actors);
|
||||
|
||||
isnot(actors.indexOf(actorPrefix), -1,
|
||||
"The test actor exists in the pool.");
|
||||
is(actors.indexOf(actorPrefix), actors.lastIndexOf(actorPrefix),
|
||||
"Only one actor exists in the pool.");
|
||||
let count = 0;
|
||||
for (let pool of extraPools) {
|
||||
count += Object.keys(pool._actors).filter(e => {
|
||||
return e.startsWith(actorPrefix);
|
||||
}).length;
|
||||
}
|
||||
is(count, 2,
|
||||
"Only two actor exists in all pools. One tab actor and one global.");
|
||||
|
||||
gClient.close(finish);
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/p
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
|
||||
let { BrowserDebuggerProcess } = Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", {});
|
||||
let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
|
||||
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
@ -473,9 +473,9 @@ function initChromeDebugger(aOnClose) {
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
// Wait for the debugger process to start...
|
||||
BrowserDebuggerProcess.init(aOnClose, aProcess => {
|
||||
info("Chrome debugger process started successfully.");
|
||||
// Wait for the toolbox process to start...
|
||||
BrowserToolboxProcess.init(aOnClose, aProcess => {
|
||||
info("Browser toolbox process started successfully.");
|
||||
|
||||
prepareDebugger(aProcess);
|
||||
deferred.resolve(aProcess);
|
||||
|
@ -52,8 +52,8 @@ devtoolsCommandlineHandler.prototype = {
|
||||
return;
|
||||
}
|
||||
if (remoteDebuggingEnabled) {
|
||||
Cu.import("resource:///modules/devtools/DebuggerProcess.jsm");
|
||||
BrowserDebuggerProcess.init();
|
||||
Cu.import("resource:///modules/devtools/ToolboxProcess.jsm");
|
||||
BrowserToolboxProcess.init();
|
||||
} else {
|
||||
let errorMsg = "Could not run chrome debugger! You need the following prefs " +
|
||||
"to be set to true: " + kDebuggerPrefs.join(", ");
|
||||
@ -68,7 +68,7 @@ devtoolsCommandlineHandler.prototype = {
|
||||
},
|
||||
|
||||
helpInfo : " -jsconsole Open the Browser Console.\n" +
|
||||
" -jsdebugger Open the Browser Debugger.\n",
|
||||
" -jsdebugger Open the Browser Toolbox.\n",
|
||||
|
||||
classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const DBG_XUL = "chrome://browser/content/devtools/debugger.xul";
|
||||
const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul";
|
||||
const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@ -17,17 +17,17 @@ Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
let require = devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["BrowserDebuggerProcess"];
|
||||
this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
|
||||
|
||||
/**
|
||||
* Constructor for creating a process that will hold a chrome debugger.
|
||||
* Constructor for creating a process that will hold a chrome toolbox.
|
||||
*
|
||||
* @param function aOnClose [optional]
|
||||
* A function called when the process stops running.
|
||||
* @param function aOnRun [optional]
|
||||
* A function called when the process starts running.
|
||||
*/
|
||||
this.BrowserDebuggerProcess = function BrowserDebuggerProcess(aOnClose, aOnRun) {
|
||||
this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun) {
|
||||
this._closeCallback = aOnClose;
|
||||
this._runCallback = aOnRun;
|
||||
this._telemetry = new Telemetry();
|
||||
@ -38,19 +38,19 @@ this.BrowserDebuggerProcess = function BrowserDebuggerProcess(aOnClose, aOnRun)
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes and starts a chrome debugger process.
|
||||
* Initializes and starts a chrome toolbox process.
|
||||
* @return object
|
||||
*/
|
||||
BrowserDebuggerProcess.init = function(aOnClose, aOnRun) {
|
||||
return new BrowserDebuggerProcess(aOnClose, aOnRun);
|
||||
BrowserToolboxProcess.init = function(aOnClose, aOnRun) {
|
||||
return new BrowserToolboxProcess(aOnClose, aOnRun);
|
||||
};
|
||||
|
||||
BrowserDebuggerProcess.prototype = {
|
||||
BrowserToolboxProcess.prototype = {
|
||||
/**
|
||||
* Initializes the debugger server.
|
||||
*/
|
||||
_initServer: function() {
|
||||
dumpn("Initializing the chrome debugger server.");
|
||||
dumpn("Initializing the chrome toolbox server.");
|
||||
|
||||
if (!this.loader) {
|
||||
// Create a separate loader instance, so that we can be sure to receive a
|
||||
@ -71,7 +71,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
|
||||
this.debuggerServer.openListener(Prefs.chromeDebuggingPort);
|
||||
|
||||
dumpn("Finished initializing the chrome debugger server.");
|
||||
dumpn("Finished initializing the chrome toolbox server.");
|
||||
dumpn("Started listening on port: " + Prefs.chromeDebuggingPort);
|
||||
},
|
||||
|
||||
@ -79,7 +79,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
* Initializes a profile for the remote debugger process.
|
||||
*/
|
||||
_initProfile: function() {
|
||||
dumpn("Initializing the chrome debugger user profile.");
|
||||
dumpn("Initializing the chrome toolbox user profile.");
|
||||
|
||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
|
||||
.createInstance(Ci.nsIToolkitProfileService);
|
||||
@ -88,7 +88,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
try {
|
||||
// Attempt to get the required chrome debugging profile name string.
|
||||
profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME;
|
||||
dumpn("Using chrome debugger profile name: " + profileName);
|
||||
dumpn("Using chrome toolbox profile name: " + profileName);
|
||||
} catch (e) {
|
||||
// Requested profile string could not be retrieved.
|
||||
profileName = CHROME_DEBUGGER_PROFILE_NAME;
|
||||
@ -101,7 +101,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
try {
|
||||
// Attempt to get the required chrome debugging profile toolkit object.
|
||||
profileObject = profileService.getProfileByName(profileName);
|
||||
dumpn("Using chrome debugger profile object: " + profileObject);
|
||||
dumpn("Using chrome toolbox profile object: " + profileObject);
|
||||
|
||||
// The profile exists but the corresponding folder may have been deleted.
|
||||
var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries;
|
||||
@ -115,7 +115,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
}
|
||||
// Requested profile was found but the folder was deleted. Cleanup needed.
|
||||
profileObject.remove(true);
|
||||
dumpn("The already existing chrome debugger profile was invalid.");
|
||||
dumpn("The already existing chrome toolbox profile was invalid.");
|
||||
} catch (e) {
|
||||
// Requested profile object was not found.
|
||||
let msg = "Creating a profile failed. " + e.name + ": " + e.message;
|
||||
@ -127,7 +127,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
this._dbgProfile = profileService.createProfile(null, profileName);
|
||||
profileService.flush();
|
||||
|
||||
dumpn("Finished creating the chrome debugger user profile.");
|
||||
dumpn("Finished creating the chrome toolbox user profile.");
|
||||
dumpn("Flushed profile service with: " + profileName);
|
||||
},
|
||||
|
||||
@ -145,7 +145,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
|
||||
this._telemetry.toolOpened("jsbrowserdebugger");
|
||||
|
||||
dumpn("Chrome debugger is now running...");
|
||||
dumpn("Chrome toolbox is now running...");
|
||||
if (typeof this._runCallback == "function") {
|
||||
this._runCallback.call({}, this);
|
||||
}
|
||||
@ -164,7 +164,7 @@ BrowserDebuggerProcess.prototype = {
|
||||
this._telemetry.toolClosed("jsbrowserdebugger");
|
||||
this.debuggerServer.destroy();
|
||||
|
||||
dumpn("Chrome debugger is now closed...");
|
||||
dumpn("Chrome toolbox is now closed...");
|
||||
if (typeof this._closeCallback == "function") {
|
||||
this._closeCallback.call({}, this);
|
||||
}
|
@ -405,12 +405,12 @@ let gDevToolsBrowser = {
|
||||
let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled");
|
||||
toggleCmd("Tools:DevAppMgr", appMgrEnabled);
|
||||
|
||||
// Enable Chrome Debugger?
|
||||
// Enable Browser Toolbox?
|
||||
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
|
||||
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
|
||||
let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled &&
|
||||
Services.prefs.getBoolPref("devtools.debugger.chrome-enabled");
|
||||
toggleCmd("Tools:ChromeDebugger", remoteEnabled);
|
||||
toggleCmd("Tools:BrowserToolbox", remoteEnabled);
|
||||
|
||||
// Enable Error Console?
|
||||
let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
|
||||
|
@ -6,7 +6,7 @@
|
||||
let gItemsToTest = {
|
||||
"menu_devToolbar": "devtools.toolbar.enabled",
|
||||
"menu_devAppMgr": "devtools.appmanager.enabled",
|
||||
"menu_chromeDebugger": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled", "devtools.debugger.chrome-enabled"],
|
||||
"menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled", "devtools.debugger.chrome-enabled"],
|
||||
"javascriptConsole": "devtools.errorconsole.enabled",
|
||||
"menu_devtools_connect": "devtools.debugger.remote-enabled",
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ exports.Hosts = {
|
||||
"side": SidebarHost,
|
||||
"window": WindowHost,
|
||||
"custom": CustomHost
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Host object for the dock on the bottom of the browser
|
||||
@ -272,7 +272,7 @@ WindowHost.prototype = {
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Host object for the toolbox in its own tab
|
||||
@ -286,11 +286,14 @@ function CustomHost(hostTab, options) {
|
||||
CustomHost.prototype = {
|
||||
type: "custom",
|
||||
|
||||
_sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg) {
|
||||
_sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) {
|
||||
// It's up to the custom frame owner (parent window) to honor
|
||||
// "close" or "raise" instructions.
|
||||
let topWindow = this.frame.ownerDocument.defaultView;
|
||||
let json = {name:"toolbox-" + msg, uid: this.uid}
|
||||
let json = {name:"toolbox-" + msg, uid: this.uid};
|
||||
if (data) {
|
||||
json.data = data;
|
||||
}
|
||||
topWindow.postMessage(JSON.stringify(json), "*");
|
||||
},
|
||||
|
||||
@ -312,7 +315,7 @@ CustomHost.prototype = {
|
||||
* Set the toolbox title.
|
||||
*/
|
||||
setTitle: function CH_setTitle(title) {
|
||||
// Not supported
|
||||
this._sendMessageToTopWindow("title", { value: title });
|
||||
},
|
||||
|
||||
/**
|
||||
|
103
browser/devtools/framework/toolbox-process-window.js
Normal file
103
browser/devtools/framework/toolbox-process-window.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
let { debuggerSocketConnect, DebuggerClient } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
let { ViewHelpers } =
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
/**
|
||||
* Shortcuts for accessing various debugger preferences.
|
||||
*/
|
||||
let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
|
||||
chromeDebuggingHost: ["Char", "chrome-debugging-host"],
|
||||
chromeDebuggingPort: ["Int", "chrome-debugging-port"]
|
||||
});
|
||||
|
||||
// Initiate the connection
|
||||
let transport = debuggerSocketConnect(
|
||||
Prefs.chromeDebuggingHost,
|
||||
Prefs.chromeDebuggingPort
|
||||
);
|
||||
let client = new DebuggerClient(transport);
|
||||
client.connect(() => {
|
||||
client.listTabs(openToolbox);
|
||||
});
|
||||
|
||||
let gToolbox;
|
||||
|
||||
function openToolbox(form) {
|
||||
let options = {
|
||||
form: form,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
devtools.TargetFactory.forRemoteTab(options).then(target => {
|
||||
let frame = document.getElementById("toolbox-iframe");
|
||||
let options = { customIframe: frame };
|
||||
gDevTools.showToolbox(target,
|
||||
"jsdebugger",
|
||||
devtools.Toolbox.HostType.CUSTOM,
|
||||
options)
|
||||
.then(onNewToolbox);
|
||||
});
|
||||
}
|
||||
|
||||
function onNewToolbox(toolbox) {
|
||||
gToolbox = toolbox;
|
||||
bindToolboxHandlers();
|
||||
raise();
|
||||
}
|
||||
|
||||
function bindToolboxHandlers() {
|
||||
gToolbox.once("destroyed", quitApp);
|
||||
window.addEventListener("unload", onUnload);
|
||||
}
|
||||
|
||||
function onUnload() {
|
||||
window.removeEventListener("unload", onUnload);
|
||||
window.removeEventListener("message", onMessage);
|
||||
gToolbox.destroy();
|
||||
}
|
||||
|
||||
function onMessage(event) {
|
||||
try {
|
||||
let json = JSON.parse(event.data);
|
||||
switch (json.name) {
|
||||
case "toolbox-raise":
|
||||
raise();
|
||||
break;
|
||||
case "toolbox-title":
|
||||
setTitle(json.data.value);
|
||||
break;
|
||||
}
|
||||
} catch(e) { Cu.reportError(e); }
|
||||
}
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
|
||||
function raise() {
|
||||
window.focus();
|
||||
}
|
||||
|
||||
function setTitle(title) {
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
function quitApp() {
|
||||
let quit = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(quit, "quit-application-requested", null);
|
||||
|
||||
let shouldProceed = !quit.data;
|
||||
if (shouldProceed) {
|
||||
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
|
||||
}
|
||||
}
|
41
browser/devtools/framework/toolbox-process-window.xul
Normal file
41
browser/devtools/framework/toolbox-process-window.xul
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % toolboxDTD SYSTEM "chrome://browser/locale/devtools/toolbox.dtd" >
|
||||
%toolboxDTD;
|
||||
]>
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="devtools-toolbox-window"
|
||||
macanimationtype="document"
|
||||
fullscreenbutton="true"
|
||||
windowtype="devtools:toolbox"
|
||||
width="900" height="600"
|
||||
persist="screenX screenY width height sizemode">
|
||||
|
||||
<script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="text/javascript" src="toolbox-process-window.js"/>
|
||||
<script type="text/javascript" src="chrome://global/content/viewSourceUtils.js"/>
|
||||
<script type="text/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
||||
|
||||
<commandset id="toolbox-commandset">
|
||||
<command id="toolbox-cmd-close" oncommand="window.close();"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="toolbox-keyset">
|
||||
<key id="toolbox-key-close"
|
||||
key="&closeCmd.key;"
|
||||
command="toolbox-cmd-close"
|
||||
modifiers="accel"/>
|
||||
</keyset>
|
||||
|
||||
<!-- This will be used by the Web Console to hold any popups it may create,
|
||||
for example when viewing network request details. -->
|
||||
<popupset id="mainPopupSet"></popupset>
|
||||
|
||||
<iframe id="toolbox-iframe" flex="1"></iframe>
|
||||
</window>
|
@ -76,6 +76,8 @@ browser.jar:
|
||||
content/browser/devtools/framework/toolbox-options.js (framework/toolbox-options.js)
|
||||
* content/browser/devtools/framework/toolbox.xul (framework/toolbox.xul)
|
||||
content/browser/devtools/framework/toolbox.css (framework/toolbox.css)
|
||||
content/browser/devtools/framework/toolbox-process-window.xul (framework/toolbox-process-window.xul)
|
||||
content/browser/devtools/framework/toolbox-process-window.js (framework/toolbox-process-window.js)
|
||||
content/browser/devtools/inspector/inspector.xul (inspector/inspector.xul)
|
||||
content/browser/devtools/inspector/inspector.css (inspector/inspector.css)
|
||||
content/browser/devtools/connect.xhtml (framework/connect/connect.xhtml)
|
||||
|
@ -55,17 +55,30 @@ const EVENTS = {
|
||||
REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
|
||||
|
||||
// When the response body is displayed in the UI.
|
||||
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable"
|
||||
}
|
||||
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
|
||||
|
||||
// When `onTabSelect` is fired and subsequently rendered
|
||||
TAB_UPDATED: "NetMonitor:TabUpdated",
|
||||
|
||||
// Fired when Sidebar is finished being populated
|
||||
SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
|
||||
|
||||
// Fired when NetworkDetailsView is finished being populated
|
||||
NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
|
||||
|
||||
// Fired when NetworkDetailsView is finished being populated
|
||||
CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated"
|
||||
};
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const Editor = require("devtools/sourceeditor/editor");
|
||||
|
@ -6,7 +6,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const promise = require("sdk/core/promise");
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
function NetMonitorPanel(iframeWindow, toolbox) {
|
||||
|
@ -1382,15 +1382,19 @@ SidebarView.prototype = {
|
||||
*
|
||||
* @param object aData
|
||||
* The data source (this should be the attachment of a request item).
|
||||
* @return object
|
||||
* Returns a promise that resolves upon population of the subview.
|
||||
*/
|
||||
populate: function(aData) {
|
||||
if (aData.isCustom) {
|
||||
NetMonitorView.CustomRequest.populate(aData);
|
||||
$("#details-pane").selectedIndex = 0;
|
||||
} else {
|
||||
NetMonitorView.NetworkDetails.populate(aData);
|
||||
$("#details-pane").selectedIndex = 1;
|
||||
}
|
||||
let isCustom = aData.isCustom;
|
||||
let view = isCustom ?
|
||||
NetMonitorView.CustomRequest :
|
||||
NetMonitorView.NetworkDetails;
|
||||
|
||||
return view.populate(aData).then(() => {
|
||||
$("#details-pane").selectedIndex = isCustom ? 0 : 1
|
||||
window.emit(EVENTS.SIDEBAR_POPULATED)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1414,6 +1418,8 @@ CustomRequestView.prototype = {
|
||||
*
|
||||
* @param object aData
|
||||
* The data source (this should be the attachment of a request item).
|
||||
* @return object
|
||||
* Returns a promise that resolves upon population the view.
|
||||
*/
|
||||
populate: function(aData) {
|
||||
$("#custom-url-value").value = aData.url;
|
||||
@ -1421,15 +1427,22 @@ CustomRequestView.prototype = {
|
||||
$("#custom-headers-value").value =
|
||||
writeHeaderText(aData.requestHeaders.headers);
|
||||
|
||||
let view = this;
|
||||
let postDataPromise = null;
|
||||
|
||||
if (aData.requestPostData) {
|
||||
let body = aData.requestPostData.postData.text;
|
||||
|
||||
gNetwork.getString(body).then(aString => {
|
||||
postDataPromise = gNetwork.getString(body).then(aString => {
|
||||
$("#custom-postdata-value").value = aString;
|
||||
});
|
||||
} else {
|
||||
postDataPromise = promise.resolve();
|
||||
}
|
||||
|
||||
this.updateCustomQuery(aData.url);
|
||||
return postDataPromise
|
||||
.then(() => view.updateCustomQuery(aData.url))
|
||||
.then(() => window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1584,6 +1597,8 @@ NetworkDetailsView.prototype = {
|
||||
*
|
||||
* @param object aData
|
||||
* The data source (this should be the attachment of a request item).
|
||||
* @return object
|
||||
* Returns a promise that resolves upon population the view.
|
||||
*/
|
||||
populate: function(aData) {
|
||||
$("#request-params-box").setAttribute("flex", "1");
|
||||
@ -1601,6 +1616,9 @@ NetworkDetailsView.prototype = {
|
||||
|
||||
this._dataSrc = { src: aData, populated: [] };
|
||||
this._onTabSelect();
|
||||
window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
|
||||
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1609,35 +1627,38 @@ NetworkDetailsView.prototype = {
|
||||
_onTabSelect: function() {
|
||||
let { src, populated } = this._dataSrc || {};
|
||||
let tab = this.widget.selectedIndex;
|
||||
let view = this;
|
||||
|
||||
// Make sure the data source is valid and don't populate the same tab twice.
|
||||
if (!src || populated[tab]) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab) {
|
||||
case 0: // "Headers"
|
||||
this._setSummary(src);
|
||||
this._setResponseHeaders(src.responseHeaders);
|
||||
this._setRequestHeaders(src.requestHeaders);
|
||||
break;
|
||||
case 1: // "Cookies"
|
||||
this._setResponseCookies(src.responseCookies);
|
||||
this._setRequestCookies(src.requestCookies);
|
||||
break;
|
||||
case 2: // "Params"
|
||||
this._setRequestGetParams(src.url);
|
||||
this._setRequestPostParams(src.requestHeaders, src.requestPostData);
|
||||
break;
|
||||
case 3: // "Response"
|
||||
this._setResponseBody(src.url, src.responseContent);
|
||||
break;
|
||||
case 4: // "Timings"
|
||||
this._setTimingsInformation(src.eventTimings);
|
||||
break;
|
||||
}
|
||||
|
||||
populated[tab] = true;
|
||||
Task.spawn(function*() {
|
||||
switch (tab) {
|
||||
case 0: // "Headers"
|
||||
yield view._setSummary(src);
|
||||
yield view._setResponseHeaders(src.responseHeaders);
|
||||
yield view._setRequestHeaders(src.requestHeaders);
|
||||
break;
|
||||
case 1: // "Cookies"
|
||||
yield view._setResponseCookies(src.responseCookies);
|
||||
yield view._setRequestCookies(src.requestCookies);
|
||||
break;
|
||||
case 2: // "Params"
|
||||
yield view._setRequestGetParams(src.url);
|
||||
yield view._setRequestPostParams(src.requestHeaders, src.requestPostData);
|
||||
break;
|
||||
case 3: // "Response"
|
||||
yield view._setResponseBody(src.url, src.responseContent);
|
||||
break;
|
||||
case 4: // "Timings"
|
||||
yield view._setTimingsInformation(src.eventTimings);
|
||||
break;
|
||||
}
|
||||
populated[tab] = true;
|
||||
window.emit(EVENTS.TAB_UPDATED);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1684,11 +1705,14 @@ NetworkDetailsView.prototype = {
|
||||
*
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that resolves when request headers are set.
|
||||
*/
|
||||
_setRequestHeaders: function(aResponse) {
|
||||
if (aResponse && aResponse.headers.length) {
|
||||
this._addHeaders(this._requestHeaders, aResponse);
|
||||
return this._addHeaders(this._requestHeaders, aResponse);
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1696,12 +1720,15 @@ NetworkDetailsView.prototype = {
|
||||
*
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that resolves when response headers are set.
|
||||
*/
|
||||
_setResponseHeaders: function(aResponse) {
|
||||
if (aResponse && aResponse.headers.length) {
|
||||
aResponse.headers.sort((a, b) => a.name > b.name);
|
||||
this._addHeaders(this._responseHeaders, aResponse);
|
||||
return this._addHeaders(this._responseHeaders, aResponse);
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1711,6 +1738,8 @@ NetworkDetailsView.prototype = {
|
||||
* The type of headers to populate (request or response).
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that resolves when headers are added.
|
||||
*/
|
||||
_addHeaders: function(aName, aResponse) {
|
||||
let kb = aResponse.headersSize / 1024;
|
||||
@ -1719,10 +1748,11 @@ NetworkDetailsView.prototype = {
|
||||
let headersScope = this._headers.addScope(aName + " (" + text + ")");
|
||||
headersScope.expanded = true;
|
||||
|
||||
for (let header of aResponse.headers) {
|
||||
return promise.all(aResponse.headers.map(header => {
|
||||
let headerVar = headersScope.addItem(header.name, {}, true);
|
||||
gNetwork.getString(header.value).then(aString => headerVar.setGrip(aString));
|
||||
}
|
||||
return gNetwork.getString(header.value)
|
||||
.then(aString => headerVar.setGrip(aString));
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1730,12 +1760,15 @@ NetworkDetailsView.prototype = {
|
||||
*
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that is resolved when the request cookies are set.
|
||||
*/
|
||||
_setRequestCookies: function(aResponse) {
|
||||
if (aResponse && aResponse.cookies.length) {
|
||||
aResponse.cookies.sort((a, b) => a.name > b.name);
|
||||
this._addCookies(this._requestCookies, aResponse);
|
||||
return this._addCookies(this._requestCookies, aResponse);
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1743,11 +1776,14 @@ NetworkDetailsView.prototype = {
|
||||
*
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that is resolved when the response cookies are set.
|
||||
*/
|
||||
_setResponseCookies: function(aResponse) {
|
||||
if (aResponse && aResponse.cookies.length) {
|
||||
this._addCookies(this._responseCookies, aResponse);
|
||||
return this._addCookies(this._responseCookies, aResponse);
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1757,33 +1793,37 @@ NetworkDetailsView.prototype = {
|
||||
* The type of cookies to populate (request or response).
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* Returns a promise that resolves upon the adding of cookies.
|
||||
*/
|
||||
_addCookies: function(aName, aResponse) {
|
||||
let cookiesScope = this._cookies.addScope(aName);
|
||||
cookiesScope.expanded = true;
|
||||
|
||||
for (let cookie of aResponse.cookies) {
|
||||
return promise.all(aResponse.cookies.map(cookie => {
|
||||
let cookieVar = cookiesScope.addItem(cookie.name, {}, true);
|
||||
gNetwork.getString(cookie.value).then(aString => cookieVar.setGrip(aString));
|
||||
return gNetwork.getString(cookie.value).then(aString => {
|
||||
cookieVar.setGrip(aString);
|
||||
|
||||
// By default the cookie name and value are shown. If this is the only
|
||||
// information available, then nothing else is to be displayed.
|
||||
let cookieProps = Object.keys(cookie);
|
||||
if (cookieProps.length == 2) {
|
||||
continue;
|
||||
}
|
||||
// By default the cookie name and value are shown. If this is the only
|
||||
// information available, then nothing else is to be displayed.
|
||||
let cookieProps = Object.keys(cookie);
|
||||
if (cookieProps.length == 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display any other information other than the cookie name and value
|
||||
// which may be available.
|
||||
let rawObject = Object.create(null);
|
||||
let otherProps = cookieProps.filter(e => e != "name" && e != "value");
|
||||
for (let prop of otherProps) {
|
||||
rawObject[prop] = cookie[prop];
|
||||
}
|
||||
cookieVar.populate(rawObject);
|
||||
cookieVar.twisty = true;
|
||||
cookieVar.expanded = true;
|
||||
}
|
||||
// Display any other information other than the cookie name and value
|
||||
// which may be available.
|
||||
let rawObject = Object.create(null);
|
||||
let otherProps = cookieProps.filter(e => e != "name" && e != "value");
|
||||
for (let prop of otherProps) {
|
||||
rawObject[prop] = cookie[prop];
|
||||
}
|
||||
cookieVar.populate(rawObject);
|
||||
cookieVar.twisty = true;
|
||||
cookieVar.expanded = true;
|
||||
});
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1806,12 +1846,14 @@ NetworkDetailsView.prototype = {
|
||||
* The "requestHeaders" message received from the server.
|
||||
* @param object aPostDataResponse
|
||||
* The "requestPostData" message received from the server.
|
||||
* @return object
|
||||
* A promise that is resolved when the request post params are set.
|
||||
*/
|
||||
_setRequestPostParams: function(aHeadersResponse, aPostDataResponse) {
|
||||
if (!aHeadersResponse || !aPostDataResponse) {
|
||||
return;
|
||||
return promise.resolve();
|
||||
}
|
||||
gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
|
||||
return gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
|
||||
// Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
|
||||
let cType = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
|
||||
let cString = cType ? cType.value : "";
|
||||
@ -1833,12 +1875,11 @@ NetworkDetailsView.prototype = {
|
||||
paramsScope.locked = true;
|
||||
|
||||
$("#request-post-data-textarea-box").hidden = false;
|
||||
NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
|
||||
return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
|
||||
aEditor.setText(aString);
|
||||
});
|
||||
}
|
||||
window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
});
|
||||
}).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1870,14 +1911,16 @@ NetworkDetailsView.prototype = {
|
||||
* The request's url.
|
||||
* @param object aResponse
|
||||
* The message received from the server.
|
||||
* @return object
|
||||
* A promise that is resolved when the response body is set
|
||||
*/
|
||||
_setResponseBody: function(aUrl, aResponse) {
|
||||
if (!aResponse) {
|
||||
return;
|
||||
return promise.resolve();
|
||||
}
|
||||
let { mimeType, text, encoding } = aResponse.content;
|
||||
|
||||
gNetwork.getString(text).then(aString => {
|
||||
return gNetwork.getString(text).then(aString => {
|
||||
// Handle json, which we tentatively identify by checking the MIME type
|
||||
// for "json" after any word boundary. This works for the standard
|
||||
// "application/json", and also for custom types like "x-bigcorp-json".
|
||||
@ -1903,22 +1946,22 @@ NetworkDetailsView.prototype = {
|
||||
? L10N.getFormatStr("jsonpScopeName", callbackPadding[0].slice(0, -1))
|
||||
: L10N.getStr("jsonScopeName");
|
||||
|
||||
this._json.controller.setSingleVariable({
|
||||
return this._json.controller.setSingleVariable({
|
||||
label: jsonScopeName,
|
||||
rawObject: jsonObject,
|
||||
});
|
||||
}).expanded;
|
||||
}
|
||||
// Malformed JSON.
|
||||
else {
|
||||
$("#response-content-textarea-box").hidden = false;
|
||||
NetMonitorView.editor("#response-content-textarea").then(aEditor => {
|
||||
aEditor.setMode(Editor.modes.js);
|
||||
aEditor.setText(aString);
|
||||
});
|
||||
let infoHeader = $("#response-content-info-header");
|
||||
infoHeader.setAttribute("value", parsingError);
|
||||
infoHeader.setAttribute("tooltiptext", parsingError);
|
||||
infoHeader.hidden = false;
|
||||
return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
|
||||
aEditor.setMode(Editor.modes.js);
|
||||
aEditor.setText(aString);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Handle images.
|
||||
@ -1948,7 +1991,7 @@ NetworkDetailsView.prototype = {
|
||||
// Handle anything else.
|
||||
else {
|
||||
$("#response-content-textarea-box").hidden = false;
|
||||
NetMonitorView.editor("#response-content-textarea").then(aEditor => {
|
||||
return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
|
||||
aEditor.setMode(Editor.modes.text);
|
||||
aEditor.setText(aString);
|
||||
|
||||
@ -1964,8 +2007,7 @@ NetworkDetailsView.prototype = {
|
||||
}
|
||||
});
|
||||
}
|
||||
window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
});
|
||||
}).then(() => window.emit(EVENTS.RESPONSE_BODY_DISPLAYED));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -75,31 +75,27 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
testResponseTab("xml")
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
return testResponseTab("css");
|
||||
})
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 2;
|
||||
return testResponseTab("js");
|
||||
})
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 3;
|
||||
return testResponseTab("json");
|
||||
})
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 4;
|
||||
return testResponseTab("html");
|
||||
})
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 5;
|
||||
return testResponseTab("png");
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
.then(finish);
|
||||
Task.spawn(function*() {
|
||||
yield waitForResponseBodyDisplayed();
|
||||
yield testResponseTab("xml");
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
yield waitForTabUpdated();
|
||||
yield testResponseTab("css");
|
||||
RequestsMenu.selectedIndex = 2;
|
||||
yield waitForTabUpdated();
|
||||
yield testResponseTab("js");
|
||||
RequestsMenu.selectedIndex = 3;
|
||||
yield waitForTabUpdated();
|
||||
yield testResponseTab("json");
|
||||
RequestsMenu.selectedIndex = 4;
|
||||
yield waitForTabUpdated();
|
||||
yield testResponseTab("html");
|
||||
RequestsMenu.selectedIndex = 5;
|
||||
yield waitForTabUpdated();
|
||||
yield testResponseTab("png");
|
||||
yield teardown(aMonitor);
|
||||
finish();
|
||||
});
|
||||
|
||||
function testResponseTab(aType) {
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
@ -221,6 +217,14 @@ function test() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function waitForTabUpdated () {
|
||||
return waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
|
||||
}
|
||||
|
||||
function waitForResponseBodyDisplayed () {
|
||||
return waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
}
|
||||
});
|
||||
|
||||
aDebuggee.performRequests();
|
||||
|
@ -26,7 +26,10 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
|
||||
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() =>
|
||||
NetMonitorView.editor("#response-content-textarea")
|
||||
).then((aEditor) => {
|
||||
is(aEditor.getText().indexOf("\u044F"), 26, // я
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), Editor.modes.text,
|
||||
|
@ -27,7 +27,10 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
|
||||
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() =>
|
||||
NetMonitorView.editor("#response-content-textarea")
|
||||
).then((aEditor) => {
|
||||
is(aEditor.getText().indexOf("\u044F"), 302, // я
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), Editor.modes.html,
|
||||
|
@ -31,38 +31,41 @@ function test() {
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
|
||||
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() => {
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("value"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
|
||||
"The response info header doesn't have the intended value attribute.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("tooltiptext"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
|
||||
"The response info header doesn't have the intended tooltiptext attribute.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("value"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
|
||||
"The response info header doesn't have the intended value attribute.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("tooltiptext"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
|
||||
"The response info header doesn't have the intended tooltiptext attribute.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
is(aEditor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), Editor.modes.js,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
is(aEditor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), Editor.modes.js,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -30,8 +30,11 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
testResponseTab();
|
||||
teardown(aMonitor).then(finish);
|
||||
let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
|
||||
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED)
|
||||
.then(testResponseTab)
|
||||
.then(() => teardown(aMonitor))
|
||||
.then(finish);
|
||||
|
||||
function testResponseTab() {
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
|
@ -30,8 +30,11 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
testResponseTab();
|
||||
teardown(aMonitor).then(finish);
|
||||
let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
|
||||
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED)
|
||||
.then(testResponseTab)
|
||||
.then(() => teardown(aMonitor))
|
||||
.then(finish);
|
||||
|
||||
function testResponseTab() {
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
|
@ -40,14 +40,14 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[2]);
|
||||
|
||||
testParamsTab("urlencoded")
|
||||
.then(() => {
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
return testParamsTab("multipart");
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
|
||||
waitFor(aMonitor.panelWin, TAB_UPDATED).then(() =>
|
||||
testParamsTab("urlencoded")
|
||||
).then(() => {
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
return waitFor(aMonitor.panelWin, TAB_UPDATED);
|
||||
}).then(() => testParamsTab("multipart"))
|
||||
.then(() => teardown(aMonitor))
|
||||
.then(finish);
|
||||
|
||||
function testParamsTab(aType) {
|
||||
|
@ -20,41 +20,43 @@ function test() {
|
||||
NetMonitorView.toggleDetailsPane({ visible: true }, 2)
|
||||
RequestsMenu.selectedIndex = 0;
|
||||
|
||||
let tab = document.querySelectorAll("#event-details-pane tab")[2];
|
||||
let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
|
||||
let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
|
||||
waitFor(aMonitor.panelWin, TAB_UPDATED).then(() => {
|
||||
let tab = document.querySelectorAll("#event-details-pane tab")[2];
|
||||
let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
|
||||
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The params tab in the network details pane should be selected.");
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The params tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#request-params-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The request params box doesn't have the indended visibility.");
|
||||
is(tabpanel.querySelector("#request-post-data-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The request post data textarea box doesn't have the indended visibility.");
|
||||
is(tabpanel.querySelector("#request-params-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The request params box doesn't have the indended visibility.");
|
||||
is(tabpanel.querySelector("#request-post-data-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The request post data textarea box doesn't have the indended visibility.");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 param scopes displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 param scopes displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
is(postScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("paramsFormData"),
|
||||
"The post scope doesn't have the correct title.");
|
||||
let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
is(postScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("paramsFormData"),
|
||||
"The post scope doesn't have the correct title.");
|
||||
|
||||
is(postScope.querySelectorAll(".variables-view-variable").length, 2,
|
||||
"There should be 2 param values displayed in the post scope.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
|
||||
"foo", "The first query param name was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
|
||||
"\"bar\"", "The first query param value was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
|
||||
"baz", "The second query param name was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
|
||||
"\"123\"", "The second query param value was incorrect.");
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
is(postScope.querySelectorAll(".variables-view-variable").length, 2,
|
||||
"There should be 2 param values displayed in the post scope.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
|
||||
"foo", "The first query param name was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
|
||||
"\"bar\"", "The first query param value was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
|
||||
"baz", "The second query param name was incorrect.");
|
||||
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
|
||||
"\"123\"", "The second query param value was incorrect.");
|
||||
teardown(aMonitor).then(finish);
|
||||
});
|
||||
});
|
||||
|
||||
aDebuggee.performRequests();
|
||||
|
@ -21,6 +21,8 @@ function test() {
|
||||
|
||||
let { NetMonitorView } = gPanelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
|
||||
let CUSTOMREQUESTVIEW_POPULATED = aMonitor.panelWin.EVENTS.CUSTOMREQUESTVIEW_POPULATED;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
@ -28,24 +30,28 @@ function test() {
|
||||
let origItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = origItem;
|
||||
|
||||
// add a new custom request cloned from selected request
|
||||
RequestsMenu.cloneSelectedRequest();
|
||||
testCustomForm(origItem.attachment);
|
||||
waitFor(aMonitor.panelWin, TAB_UPDATED).then(() => {
|
||||
// add a new custom request cloned from selected request
|
||||
RequestsMenu.cloneSelectedRequest();
|
||||
return waitFor(aMonitor.panelWin, CUSTOMREQUESTVIEW_POPULATED);
|
||||
}).then(() => {
|
||||
testCustomForm(origItem.attachment);
|
||||
|
||||
let customItem = RequestsMenu.selectedItem;
|
||||
testCustomItem(customItem, origItem);
|
||||
let customItem = RequestsMenu.selectedItem;
|
||||
testCustomItem(customItem, origItem);
|
||||
|
||||
// edit the custom request
|
||||
editCustomForm(() => {
|
||||
testCustomItemChanged(customItem, origItem);
|
||||
// edit the custom request
|
||||
editCustomForm(() => {
|
||||
testCustomItemChanged(customItem, origItem);
|
||||
|
||||
waitForNetworkEvents(aMonitor, 0, 1).then(() => {
|
||||
let sentItem = RequestsMenu.selectedItem;
|
||||
testSentRequest(sentItem.attachment, origItem.attachment);
|
||||
finishUp(aMonitor);
|
||||
waitForNetworkEvents(aMonitor, 0, 1).then(() => {
|
||||
let sentItem = RequestsMenu.selectedItem;
|
||||
testSentRequest(sentItem.attachment, origItem.attachment);
|
||||
finishUp(aMonitor);
|
||||
});
|
||||
// send the new request
|
||||
RequestsMenu.sendCustomRequest();
|
||||
});
|
||||
// send the new request
|
||||
RequestsMenu.sendCustomRequest();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -11,10 +11,11 @@ function test() {
|
||||
|
||||
let { document, L10N, Editor, NetMonitorView } = aMonitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 1).then(() => {
|
||||
Task.spawn(function () {
|
||||
yield waitForNetworkEvents(aMonitor, 1);
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There shouldn't be any selected item in the requests menu.");
|
||||
is(RequestsMenu.itemCount, 1,
|
||||
@ -32,15 +33,14 @@ function test() {
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
yield waitFor(aMonitor.panelWin, TAB_UPDATED)
|
||||
testHeadersTab();
|
||||
testCookiesTab();
|
||||
testParamsTab();
|
||||
testResponseTab()
|
||||
.then(() => {
|
||||
testTimingsTab();
|
||||
return teardown(aMonitor);
|
||||
})
|
||||
.then(finish);
|
||||
yield testResponseTab();
|
||||
testTimingsTab();
|
||||
yield teardown(aMonitor);
|
||||
finish();
|
||||
});
|
||||
|
||||
function testHeadersTab() {
|
||||
@ -172,26 +172,29 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
return Task.spawn(function () {
|
||||
yield waitFor(aMonitor.panelWin, TAB_UPDATED);
|
||||
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
let tab = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header should be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content json box should be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content textarea box should not be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box should be hidden.");
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
return NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header should be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content json box should be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content textarea box should not be hidden.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box should be hidden.");
|
||||
|
||||
let aEditor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(aEditor.getText(), "Hello world!",
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(aEditor.getMode(), Editor.modes.text,
|
||||
|
@ -5,7 +5,8 @@
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
@ -282,3 +283,20 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for waiting for an event to fire before resolving a promise.
|
||||
* Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
|
||||
*
|
||||
* @param object subject
|
||||
* The event emitter object that is being listened to.
|
||||
* @param string eventName
|
||||
* The name of the event to listen to.
|
||||
* @return object
|
||||
* Returns a promise that resolves upon firing of the event.
|
||||
*/
|
||||
function waitFor (subject, eventName) {
|
||||
let deferred = promise.defer();
|
||||
subject.once(eventName, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ function ResponsiveUI(aWindow, aTab)
|
||||
// Events
|
||||
this.tab.addEventListener("TabClose", this);
|
||||
this.tabContainer.addEventListener("TabSelect", this);
|
||||
this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
|
||||
this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, true);
|
||||
|
||||
this.buildUI();
|
||||
this.checkMenus();
|
||||
@ -276,7 +276,7 @@ ResponsiveUI.prototype = {
|
||||
this.stopResizing();
|
||||
|
||||
// Remove listeners.
|
||||
this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false);
|
||||
this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, true);
|
||||
this.menulist.removeEventListener("select", this.bound_presetSelected, true);
|
||||
this.tab.removeEventListener("TabClose", this);
|
||||
this.tabContainer.removeEventListener("TabSelect", this);
|
||||
|
@ -335,13 +335,29 @@ WebConsole.prototype = {
|
||||
*/
|
||||
get lastFinishedRequestCallback() HUDService.lastFinishedRequest.callback,
|
||||
|
||||
/**
|
||||
* Getter for the window that can provide various utilities that the web
|
||||
* console makes use of, like opening links, managing popups, etc. In
|
||||
* most cases, this will be |this.browserWindow|, but in some uses (such as
|
||||
* the Browser Toolbox), there is no browser window, so an alternative window
|
||||
* hosts the utilities there.
|
||||
* @type nsIDOMWindow
|
||||
*/
|
||||
get chromeUtilsWindow()
|
||||
{
|
||||
if (this.browserWindow) {
|
||||
return this.browserWindow;
|
||||
}
|
||||
return this.chromeWindow.top;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the xul:popupset that holds any popups we open.
|
||||
* @type nsIDOMElement
|
||||
*/
|
||||
get mainPopupSet()
|
||||
{
|
||||
return this.browserWindow.document.getElementById("mainPopupSet");
|
||||
return this.chromeUtilsWindow.document.getElementById("mainPopupSet");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -353,7 +369,10 @@ WebConsole.prototype = {
|
||||
return this.ui ? this.ui.outputNode : null;
|
||||
},
|
||||
|
||||
get gViewSourceUtils() this.browserWindow.gViewSourceUtils,
|
||||
get gViewSourceUtils()
|
||||
{
|
||||
return this.chromeUtilsWindow.gViewSourceUtils;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the Web Console instance.
|
||||
@ -416,7 +435,7 @@ WebConsole.prototype = {
|
||||
*/
|
||||
openLink: function WC_openLink(aLink)
|
||||
{
|
||||
this.browserWindow.openUILinkIn(aLink, "tab");
|
||||
this.chromeUtilsWindow.openUILinkIn(aLink, "tab");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -244,10 +244,10 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY scratchpad.keycode "VK_F4">
|
||||
<!ENTITY scratchpad.keytext "F4">
|
||||
|
||||
<!-- LOCALIZATION NOTE (chromeDebuggerMenu.label): This is the label for the
|
||||
- application menu item that opens the browser debugger UI in the Tools menu. -->
|
||||
<!ENTITY chromeDebuggerMenu.label "Browser Debugger">
|
||||
<!ENTITY chromeDebuggerMenu.accesskey "e">
|
||||
<!-- LOCALIZATION NOTE (browserToolboxMenu.label): This is the label for the
|
||||
- application menu item that opens the browser toolbox UI in the Tools menu. -->
|
||||
<!ENTITY browserToolboxMenu.label "Browser Toolbox">
|
||||
<!ENTITY browserToolboxMenu.accesskey "e">
|
||||
|
||||
<!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.label "Developer Toolbar">
|
||||
|
@ -70,6 +70,9 @@ detailsPane.itemsCountLabel=One item;#1 items
|
||||
mostVisitedTitle=Most Visited
|
||||
recentlyBookmarkedTitle=Recently Bookmarked
|
||||
recentTagsTitle=Recent Tags
|
||||
# LOCALIZATION NOTE (firefoxTouchTitle): this is the name of the folder used
|
||||
# to store bookmarks created in Metro mode and share bookmarks between Metro
|
||||
# and Desktop.
|
||||
firefoxTouchTitle=Firefox Touch
|
||||
|
||||
OrganizerQueryHistory=History
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
.devtools-responsiveui-toolbar {
|
||||
background: transparent;
|
||||
/* text color is textColor from dark theme, since no theme is applied to
|
||||
* the responsive toolbar.
|
||||
*/
|
||||
color: hsl(210,30%,85%);
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
|
@ -452,7 +452,7 @@ nsresult
|
||||
ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
mChannel = aNew;
|
||||
mChannel = new nsMainThreadPtrHolder<nsIChannel>(aNew);
|
||||
SetupChannelHeaders();
|
||||
return NS_OK;
|
||||
}
|
||||
@ -501,7 +501,7 @@ ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
|
||||
|
||||
CopySegmentClosure closure;
|
||||
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
||||
if (secMan && mChannel) {
|
||||
if (secMan && mChannel.get()) {
|
||||
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
|
||||
}
|
||||
closure.mResource = this;
|
||||
@ -533,7 +533,7 @@ nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
|
||||
return rv;
|
||||
NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
|
||||
|
||||
if (!mChannel) {
|
||||
if (!mChannel.get()) {
|
||||
// When we're a clone, the decoder might ask us to Open even though
|
||||
// we haven't established an mChannel (because we might not need one)
|
||||
NS_ASSERTION(!aStreamListener,
|
||||
@ -547,7 +547,7 @@ nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
|
||||
nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
|
||||
NS_ENSURE_TRUE(mChannel.get(), NS_ERROR_NULL_POINTER);
|
||||
NS_ASSERTION(!mListener, "Listener should have been removed by now");
|
||||
|
||||
if (aStreamListener) {
|
||||
@ -713,7 +713,7 @@ void ChannelMediaResource::CloseChannel()
|
||||
mListener = nullptr;
|
||||
}
|
||||
|
||||
if (mChannel) {
|
||||
if (mChannel.get()) {
|
||||
if (mSuspendCount > 0) {
|
||||
// Resume the channel before we cancel it
|
||||
PossiblyResume();
|
||||
@ -811,7 +811,7 @@ void ChannelMediaResource::Suspend(bool aCloseImmediately)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mChannel) {
|
||||
if (mChannel.get()) {
|
||||
if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
|
||||
// Kill off our channel right now, but don't tell anyone about it.
|
||||
mIgnoreClose = true;
|
||||
@ -849,7 +849,7 @@ void ChannelMediaResource::Resume()
|
||||
NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
|
||||
--mSuspendCount;
|
||||
if (mSuspendCount == 0) {
|
||||
if (mChannel) {
|
||||
if (mChannel.get()) {
|
||||
// Just wake up our existing channel
|
||||
{
|
||||
MutexAutoLock lock(mLock);
|
||||
@ -897,12 +897,14 @@ ChannelMediaResource::RecreateChannel()
|
||||
nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
|
||||
NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
|
||||
|
||||
nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
nsresult rv = NS_NewChannel(getter_AddRefs(channel),
|
||||
mURI,
|
||||
nullptr,
|
||||
loadGroup,
|
||||
nullptr,
|
||||
loadFlags);
|
||||
mChannel = new nsMainThreadPtrHolder<nsIChannel>(channel);
|
||||
|
||||
// We have cached the Content-Type, which should not change. Give a hint to
|
||||
// the channel to avoid a sniffing failure, which would be expected because we
|
||||
@ -990,7 +992,7 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
|
||||
if (mSuspendCount > 0) {
|
||||
// Close the existing channel to force the channel to be recreated at
|
||||
// the correct offset upon resume.
|
||||
if (mChannel) {
|
||||
if (mChannel.get()) {
|
||||
mIgnoreClose = true;
|
||||
CloseChannel();
|
||||
}
|
||||
@ -1340,7 +1342,7 @@ nsresult FileMediaResource::Close()
|
||||
|
||||
// Since mChennel is only accessed by main thread, there is no necessary to
|
||||
// take the lock.
|
||||
if (mChannel) {
|
||||
if (mChannel.get()) {
|
||||
mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
|
||||
mChannel = nullptr;
|
||||
}
|
||||
@ -1354,7 +1356,7 @@ already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
||||
if (!secMan || !mChannel)
|
||||
if (!secMan || !mChannel.get())
|
||||
return nullptr;
|
||||
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
|
||||
return principal.forget();
|
||||
@ -1523,7 +1525,7 @@ MediaResource::Create(MediaDecoder* aDecoder, nsIChannel* aChannel)
|
||||
void BaseMediaResource::MoveLoadsToBackground() {
|
||||
NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
|
||||
mLoadInBackground = true;
|
||||
if (!mChannel) {
|
||||
if (!mChannel.get()) {
|
||||
// No channel, resource is probably already loaded.
|
||||
return;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIChannelEventSink.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "MediaCache.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
@ -395,7 +396,7 @@ protected:
|
||||
|
||||
class BaseMediaResource : public MediaResource {
|
||||
public:
|
||||
virtual nsIURI* URI() const { return mURI; }
|
||||
virtual nsIURI* URI() const { return const_cast<nsIURI*>(mURI.get()); }
|
||||
virtual void MoveLoadsToBackground();
|
||||
|
||||
protected:
|
||||
@ -404,8 +405,8 @@ protected:
|
||||
nsIURI* aURI,
|
||||
const nsACString& aContentType) :
|
||||
mDecoder(aDecoder),
|
||||
mChannel(aChannel),
|
||||
mURI(aURI),
|
||||
mChannel(new nsMainThreadPtrHolder<nsIChannel>(aChannel)),
|
||||
mURI(new nsMainThreadPtrHolder<nsIURI>(aURI)),
|
||||
mContentType(aContentType),
|
||||
mLoadInBackground(false)
|
||||
{
|
||||
@ -438,11 +439,11 @@ protected:
|
||||
|
||||
// Channel used to download the media data. Must be accessed
|
||||
// from the main thread only.
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsMainThreadPtrHandle<nsIChannel> mChannel;
|
||||
|
||||
// URI in case the stream needs to be re-opened. Access from
|
||||
// main thread only.
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsMainThreadPtrHandle<nsIURI> mURI;
|
||||
|
||||
// Content-Type of the channel. This is copied from the nsIChannel when the
|
||||
// MediaResource is created. This is constant, so accessing from any thread
|
||||
|
@ -32,6 +32,7 @@ VideoFrame::TakeFrom(VideoFrame* aFrame)
|
||||
{
|
||||
mImage = aFrame->mImage.forget();
|
||||
mIntrinsicSize = aFrame->mIntrinsicSize;
|
||||
mForceBlack = aFrame->GetForceBlack();
|
||||
}
|
||||
|
||||
VideoChunk::VideoChunk()
|
||||
|
@ -38,7 +38,7 @@ public:
|
||||
}
|
||||
|
||||
Image* GetImage() const { return mImage; }
|
||||
void SetForceBlack(bool aForceBlack) { mForceBlack = true; }
|
||||
void SetForceBlack(bool aForceBlack) { mForceBlack = aForceBlack; }
|
||||
bool GetForceBlack() const { return mForceBlack; }
|
||||
const gfxIntSize& GetIntrinsicSize() const { return mIntrinsicSize; }
|
||||
void SetNull();
|
||||
|
@ -6,6 +6,7 @@
|
||||
#ifndef EncodedFrameContainer_H_
|
||||
#define EncodedFrameContainer_H_
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -116,7 +116,6 @@ SerializeOpusCommentHeader(const nsCString& aVendor,
|
||||
OpusTrackEncoder::OpusTrackEncoder()
|
||||
: AudioTrackEncoder()
|
||||
, mEncoder(nullptr)
|
||||
, mSourceSegment(new AudioSegment())
|
||||
, mLookahead(0)
|
||||
, mResampler(nullptr)
|
||||
{
|
||||
@ -129,8 +128,8 @@ OpusTrackEncoder::~OpusTrackEncoder()
|
||||
}
|
||||
if (mResampler) {
|
||||
speex_resampler_destroy(mResampler);
|
||||
mResampler = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -142,7 +141,7 @@ OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
|
||||
// This version of encoder API only support 1 or 2 channels,
|
||||
// So set the mChannels less or equal 2 and
|
||||
// let InterleaveTrackData downmix pcm data.
|
||||
mChannels = aChannels > 2 ? 2 : aChannels;
|
||||
mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
|
||||
|
||||
if (aChannels <= 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -195,12 +194,12 @@ OpusTrackEncoder::GetMetadata()
|
||||
{
|
||||
// Wait if mEncoder is not initialized.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mEncoder) {
|
||||
while (!mCanceled && !mInitialized) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled || mDoneEncoding) {
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -238,29 +237,30 @@ OpusTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
|
||||
// Wait if mEncoder is not initialized, or when not enough raw data, but is
|
||||
// not the end of stream nor is being canceled.
|
||||
while (!mCanceled && (!mEncoder || (mRawSegment->GetDuration() +
|
||||
mSourceSegment->GetDuration() < GetPacketDuration() &&
|
||||
while (!mCanceled && (!mInitialized || (mRawSegment.GetDuration() +
|
||||
mSourceSegment.GetDuration() < GetPacketDuration() &&
|
||||
!mEndOfStream))) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
|
||||
if (mCanceled || mDoneEncoding) {
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mSourceSegment->AppendFrom(mRawSegment);
|
||||
mSourceSegment.AppendFrom(&mRawSegment);
|
||||
|
||||
// Pad |mLookahead| samples to the end of source stream to prevent lost of
|
||||
// original data, the pcm duration will be calculated at rate 48K later.
|
||||
if (mEndOfStream) {
|
||||
mSourceSegment->AppendNullData(mLookahead);
|
||||
if (mEndOfStream && !mEosSetInEncoder) {
|
||||
mEosSetInEncoder = true;
|
||||
mSourceSegment.AppendNullData(mLookahead);
|
||||
}
|
||||
}
|
||||
|
||||
// Start encoding data.
|
||||
nsAutoTArray<AudioDataValue, 9600> pcm;
|
||||
pcm.SetLength(GetPacketDuration() * mChannels);
|
||||
AudioSegment::ChunkIterator iter(*mSourceSegment);
|
||||
AudioSegment::ChunkIterator iter(mSourceSegment);
|
||||
int frameCopied = 0;
|
||||
while (!iter.IsEnded() && frameCopied < GetPacketDuration()) {
|
||||
AudioChunk chunk = *iter;
|
||||
@ -319,12 +319,12 @@ OpusTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
// Remove the raw data which has been pulled to pcm buffer.
|
||||
// The value of frameCopied should equal to (or smaller than, if eos)
|
||||
// GetPacketDuration().
|
||||
mSourceSegment->RemoveLeading(frameCopied);
|
||||
mSourceSegment.RemoveLeading(frameCopied);
|
||||
|
||||
// Has reached the end of input stream and all queued data has pulled for
|
||||
// encoding.
|
||||
if (mSourceSegment->GetDuration() == 0 && mEndOfStream) {
|
||||
mDoneEncoding = true;
|
||||
if (mSourceSegment.GetDuration() == 0 && mEndOfStream) {
|
||||
mEncodingComplete = true;
|
||||
LOG("[Opus] Done encoding.");
|
||||
}
|
||||
|
||||
@ -353,7 +353,7 @@ OpusTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
if (result < 0) {
|
||||
LOG("[Opus] Fail to encode data! Result: %s.", opus_strerror(result));
|
||||
}
|
||||
if (mDoneEncoding) {
|
||||
if (mEncodingComplete) {
|
||||
if (mResampler) {
|
||||
speex_resampler_destroy(mResampler);
|
||||
mResampler = nullptr;
|
||||
|
@ -37,7 +37,7 @@ public:
|
||||
nsresult GetEncodedTrack(EncodedFrameContainer& aData) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
int GetPacketDuration() MOZ_OVERRIDE;
|
||||
int GetPacketDuration();
|
||||
|
||||
nsresult Init(int aChannels, int aSamplingRate) MOZ_OVERRIDE;
|
||||
|
||||
@ -54,11 +54,12 @@ private:
|
||||
OpusEncoder* mEncoder;
|
||||
|
||||
/**
|
||||
* A local segment queue which stores the raw segments. Opus encoder only
|
||||
* takes GetPacketDuration() samples from mSourceSegment in every encoding
|
||||
* cycle, thus it needs to store the raw track data.
|
||||
* A local segment queue which takes the raw data out from mRawSegment in the
|
||||
* call of GetEncodedTrack(). Opus encoder only accepts GetPacketDuration()
|
||||
* samples from mSourceSegment every encoding cycle, thus it needs to be
|
||||
* global in order to store the leftover segments taken from mRawSegment.
|
||||
*/
|
||||
nsAutoPtr<AudioSegment> mSourceSegment;
|
||||
AudioSegment mSourceSegment;
|
||||
|
||||
/**
|
||||
* Total samples of delay added by codec, can be queried by the encoder. From
|
||||
|
@ -3,21 +3,25 @@
|
||||
* 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 "TrackEncoder.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "AudioChannelFormat.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
#undef LOG
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediakEncoder", ## args);
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args, ...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static const int DEFAULT_CHANNELS = 1;
|
||||
static const int DEFAULT_SAMPLING_RATE = 16000;
|
||||
static const int DEFAULT_CHANNELS = 1;
|
||||
static const int DEFAULT_SAMPLING_RATE = 16000;
|
||||
static const int DEFAULT_FRAME_WIDTH = 640;
|
||||
static const int DEFAULT_FRAME_HEIGHT = 480;
|
||||
static const int DEFAULT_TRACK_RATE = USECS_PER_S;
|
||||
|
||||
void
|
||||
AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
@ -31,12 +35,11 @@ AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
return;
|
||||
}
|
||||
|
||||
AudioSegment* audio = const_cast<AudioSegment*>
|
||||
(static_cast<const AudioSegment*>(&aQueuedMedia));
|
||||
const AudioSegment& audio = static_cast<const AudioSegment&>(aQueuedMedia);
|
||||
|
||||
// Check and initialize parameters for codec encoder.
|
||||
if (!mInitialized) {
|
||||
AudioSegment::ChunkIterator iter(*audio);
|
||||
AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(audio));
|
||||
while (!iter.IsEnded()) {
|
||||
AudioChunk chunk = *iter;
|
||||
|
||||
@ -49,17 +52,15 @@ AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
NotifyCancel();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
mSilentDuration += chunk.mDuration;
|
||||
}
|
||||
|
||||
iter.Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Append and consume this raw segment.
|
||||
if (mInitialized) {
|
||||
AppendAudioSegment(audio);
|
||||
}
|
||||
AppendAudioSegment(audio);
|
||||
|
||||
|
||||
// The stream has stopped and reached the end of track.
|
||||
if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
|
||||
@ -68,24 +69,13 @@ AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrackEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
|
||||
{
|
||||
// In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
|
||||
LOG("[AudioTrackEncoder]: NotifyRemoved.");
|
||||
NotifyEndOfStream();
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrackEncoder::NotifyEndOfStream()
|
||||
{
|
||||
// If source audio chunks are completely silent till the end of encoding,
|
||||
// initialize the encoder with default channel counts and sampling rate, and
|
||||
// append this many null data to the segment of track encoder.
|
||||
// If source audio track is completely silent till the end of encoding,
|
||||
// initialize the encoder with default channel counts and sampling rate.
|
||||
if (!mCanceled && !mInitialized) {
|
||||
Init(DEFAULT_CHANNELS, DEFAULT_SAMPLING_RATE);
|
||||
mRawSegment->AppendNullData(mSilentDuration);
|
||||
mSilentDuration = 0;
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
@ -94,27 +84,19 @@ AudioTrackEncoder::NotifyEndOfStream()
|
||||
}
|
||||
|
||||
nsresult
|
||||
AudioTrackEncoder::AppendAudioSegment(MediaSegment* aSegment)
|
||||
AudioTrackEncoder::AppendAudioSegment(const AudioSegment& aSegment)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
AudioSegment* audio = static_cast<AudioSegment*>(aSegment);
|
||||
AudioSegment::ChunkIterator iter(*audio);
|
||||
|
||||
// Append this many null data to our queued segment if there is a complete
|
||||
// silence before the audio track encoder has initialized.
|
||||
if (mSilentDuration > 0) {
|
||||
mRawSegment->AppendNullData(mSilentDuration);
|
||||
mSilentDuration = 0;
|
||||
}
|
||||
|
||||
AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(aSegment));
|
||||
while (!iter.IsEnded()) {
|
||||
AudioChunk chunk = *iter;
|
||||
// Append and consume both non-null and null chunks.
|
||||
mRawSegment->AppendAndConsumeChunk(&chunk);
|
||||
mRawSegment.AppendAndConsumeChunk(&chunk);
|
||||
iter.Next();
|
||||
}
|
||||
if (mRawSegment->GetDuration() >= GetPacketDuration()) {
|
||||
|
||||
if (mRawSegment.GetDuration() >= GetPacketDuration()) {
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
@ -145,4 +127,103 @@ AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia)
|
||||
{
|
||||
if (mCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
|
||||
|
||||
// Check and initialize parameters for codec encoder.
|
||||
if (!mInitialized) {
|
||||
VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(video));
|
||||
while (!iter.IsEnded()) {
|
||||
VideoChunk chunk = *iter;
|
||||
if (!chunk.IsNull()) {
|
||||
gfxIntSize imgsize = chunk.mFrame.GetImage()->GetSize();
|
||||
int width = (imgsize.width + 1) / 2 * 2;
|
||||
int height = (imgsize.height + 1) / 2 * 2;
|
||||
nsresult rv = Init(width, height, aTrackRate);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
|
||||
NotifyCancel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
iter.Next();
|
||||
}
|
||||
}
|
||||
|
||||
AppendVideoSegment(video);
|
||||
|
||||
// The stream has stopped and reached the end of track.
|
||||
if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
|
||||
LOG("[VideoTrackEncoder]: Receive TRACK_EVENT_ENDED .");
|
||||
NotifyEndOfStream();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nsresult
|
||||
VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
// Append all video segments from MediaStreamGraph, including null an
|
||||
// non-null frames.
|
||||
VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(aSegment));
|
||||
while (!iter.IsEnded()) {
|
||||
VideoChunk chunk = *iter;
|
||||
nsRefPtr<layers::Image> image = chunk.mFrame.GetImage();
|
||||
mRawSegment.AppendFrame(image.forget(), chunk.GetDuration(),
|
||||
chunk.mFrame.GetIntrinsicSize());
|
||||
iter.Next();
|
||||
}
|
||||
|
||||
if (mRawSegment.GetDuration() > 0) {
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
VideoTrackEncoder::NotifyEndOfStream()
|
||||
{
|
||||
// If source video track is muted till the end of encoding, initialize the
|
||||
// encoder with default frame width, frame height, and track rate.
|
||||
if (!mCanceled && !mInitialized) {
|
||||
Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_TRACK_RATE);
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mEndOfStream = true;
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
void
|
||||
VideoTrackEncoder::CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(aOutputBuffer);
|
||||
|
||||
// Supports YUV420 image format only.
|
||||
int yPlaneLen = mFrameWidth * mFrameHeight;
|
||||
int cbcrPlaneLen = yPlaneLen / 2;
|
||||
int frameLen = yPlaneLen + cbcrPlaneLen;
|
||||
|
||||
aOutputBuffer->SetLength(frameLen);
|
||||
// Fill Y plane.
|
||||
memset(aOutputBuffer->Elements(), 0x10, yPlaneLen);
|
||||
// Fill Cb/Cr planes.
|
||||
memset(aOutputBuffer->Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,9 +9,10 @@
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
#include "AudioSegment.h"
|
||||
#include "EncodedFrameContainer.h"
|
||||
#include "StreamBuffer.h"
|
||||
#include "TrackMetadataBase.h"
|
||||
#include "EncodedFrameContainer.h"
|
||||
#include "VideoSegment.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -30,7 +31,15 @@ class MediaStreamGraph;
|
||||
class TrackEncoder
|
||||
{
|
||||
public:
|
||||
TrackEncoder() {}
|
||||
TrackEncoder()
|
||||
: mReentrantMonitor("media.TrackEncoder")
|
||||
, mEncodingComplete(false)
|
||||
, mEosSetInEncoder(false)
|
||||
, mInitialized(false)
|
||||
, mEndOfStream(false)
|
||||
, mCanceled(false)
|
||||
{}
|
||||
|
||||
virtual ~TrackEncoder() {}
|
||||
|
||||
/**
|
||||
@ -47,47 +56,25 @@ public:
|
||||
* Notified by the same callback of MediaEncoder when it has been removed from
|
||||
* MediaStreamGraph. Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
virtual void NotifyRemoved(MediaStreamGraph* aGraph) = 0;
|
||||
void NotifyRemoved(MediaStreamGraph* aGraph) { NotifyEndOfStream(); }
|
||||
|
||||
/**
|
||||
* Creates and sets up meta data for a specific codec
|
||||
* Creates and sets up meta data for a specific codec, called on the worker
|
||||
* thread.
|
||||
*/
|
||||
virtual already_AddRefed<TrackMetadataBase> GetMetadata() = 0;
|
||||
|
||||
/**
|
||||
* Encodes raw segments. Result data is returned in aData.
|
||||
* Encodes raw segments. Result data is returned in aData, and called on the
|
||||
* worker thread.
|
||||
*/
|
||||
virtual nsresult GetEncodedTrack(EncodedFrameContainer& aData) = 0;
|
||||
};
|
||||
|
||||
class AudioTrackEncoder : public TrackEncoder
|
||||
{
|
||||
public:
|
||||
AudioTrackEncoder()
|
||||
: TrackEncoder()
|
||||
, mChannels(0)
|
||||
, mSamplingRate(0)
|
||||
, mInitialized(false)
|
||||
, mDoneEncoding(false)
|
||||
, mReentrantMonitor("media.AudioEncoder")
|
||||
, mRawSegment(new AudioSegment())
|
||||
, mEndOfStream(false)
|
||||
, mCanceled(false)
|
||||
, mSilentDuration(0)
|
||||
{}
|
||||
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
|
||||
|
||||
void NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
|
||||
|
||||
bool IsEncodingComplete()
|
||||
{
|
||||
return mDoneEncoding;
|
||||
}
|
||||
/**
|
||||
* True if the track encoder has encoded all source segments coming from
|
||||
* MediaStreamGraph. Call on the worker thread.
|
||||
*/
|
||||
bool IsEncodingComplete() { return mEncodingComplete; }
|
||||
|
||||
/**
|
||||
* Notifies from MediaEncoder to cancel the encoding, and wakes up
|
||||
@ -100,13 +87,74 @@ public:
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Notifies track encoder that we have reached the end of source stream, and
|
||||
* wakes up mReentrantMonitor if encoder is waiting for any source data.
|
||||
*/
|
||||
virtual void NotifyEndOfStream() = 0;
|
||||
|
||||
/**
|
||||
* A ReentrantMonitor to protect the pushing and pulling of mRawSegment which
|
||||
* is declared in its subclasses, and the following flags: mInitialized,
|
||||
* EndOfStream and mCanceled. The control of protection is managed by its
|
||||
* subclasses.
|
||||
*/
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
|
||||
/**
|
||||
* True if the track encoder has encoded all source data.
|
||||
*/
|
||||
bool mEncodingComplete;
|
||||
|
||||
/**
|
||||
* True if flag of EOS or any form of indicating EOS has set in the codec-
|
||||
* encoder.
|
||||
*/
|
||||
bool mEosSetInEncoder;
|
||||
|
||||
/**
|
||||
* True if the track encoder has initialized successfully, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mInitialized;
|
||||
|
||||
/**
|
||||
* True if the TrackEncoder has received an event of TRACK_EVENT_ENDED from
|
||||
* MediaStreamGraph, or the MediaEncoder is removed from its source stream,
|
||||
* protected by mReentrantMonitor.
|
||||
*/
|
||||
bool mEndOfStream;
|
||||
|
||||
/**
|
||||
* True if a cancellation of encoding is sent from MediaEncoder, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mCanceled;
|
||||
};
|
||||
|
||||
class AudioTrackEncoder : public TrackEncoder
|
||||
{
|
||||
public:
|
||||
AudioTrackEncoder()
|
||||
: TrackEncoder()
|
||||
, mChannels(0)
|
||||
, mSamplingRate(0)
|
||||
{}
|
||||
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Number of samples per channel in a pcm buffer. This is also the value of
|
||||
* frame size required by audio encoder, and mReentrantMonitor will be
|
||||
* notified when at least this much data has been added to mRawSegment.
|
||||
*/
|
||||
virtual int GetPacketDuration() = 0;
|
||||
virtual int GetPacketDuration() { return 0; }
|
||||
|
||||
/**
|
||||
* Initializes the audio encoder. The call of this method is delayed until we
|
||||
@ -123,13 +171,13 @@ protected:
|
||||
* least GetPacketDuration() data has been added to mRawSegment, wake up other
|
||||
* method which is waiting for more data from mRawSegment.
|
||||
*/
|
||||
nsresult AppendAudioSegment(MediaSegment* aSegment);
|
||||
nsresult AppendAudioSegment(const AudioSegment& aSegment);
|
||||
|
||||
/**
|
||||
* Notifies the audio encoder that we have reached the end of source stream,
|
||||
* and wakes up mReentrantMonitor if encoder is waiting for more track data.
|
||||
*/
|
||||
void NotifyEndOfStream();
|
||||
virtual void NotifyEndOfStream() MOZ_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Interleaves the track data and stores the result into aOutput. Might need
|
||||
@ -147,43 +195,99 @@ protected:
|
||||
* This value also be used to initialize the audio encoder.
|
||||
*/
|
||||
int mChannels;
|
||||
int mSamplingRate;
|
||||
bool mInitialized;
|
||||
bool mDoneEncoding;
|
||||
|
||||
/**
|
||||
* A ReentrantMonitor to protect the pushing and pulling of mRawSegment.
|
||||
* The sampling rate of source audio data.
|
||||
*/
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
int mSamplingRate;
|
||||
|
||||
/**
|
||||
* A segment queue of audio track data, protected by mReentrantMonitor.
|
||||
*/
|
||||
nsAutoPtr<AudioSegment> mRawSegment;
|
||||
|
||||
/**
|
||||
* True if we have received an event of TRACK_EVENT_ENDED from MediaStreamGraph,
|
||||
* or the MediaEncoder is removed from its source stream, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mEndOfStream;
|
||||
|
||||
/**
|
||||
* True if a cancellation of encoding is sent from MediaEncoder, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mCanceled;
|
||||
|
||||
/**
|
||||
* The total duration of null chunks we have received from MediaStreamGraph
|
||||
* before initializing the audio track encoder.
|
||||
*/
|
||||
TrackTicks mSilentDuration;
|
||||
AudioSegment mRawSegment;
|
||||
};
|
||||
|
||||
class VideoTrackEncoder : public TrackEncoder
|
||||
{
|
||||
public:
|
||||
VideoTrackEncoder()
|
||||
: TrackEncoder()
|
||||
, mFrameWidth(0)
|
||||
, mFrameHeight(0)
|
||||
, mTrackRate(0)
|
||||
, mTotalFrameDuration(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Notified by the same callbcak of MediaEncoder when it has received a track
|
||||
* change from MediaStreamGraph. Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initialized the video encoder. In order to collect the value of width and
|
||||
* height of source frames, this initialization is delayed until we have
|
||||
* received the first valid video frame from MediaStreamGraph;
|
||||
* mReentrantMonitor will be notified after it has successfully initialized,
|
||||
* and this method is called on the MediaStramGraph thread.
|
||||
*/
|
||||
virtual nsresult Init(int aWidth, int aHeight, TrackRate aTrackRate) = 0;
|
||||
|
||||
/**
|
||||
* Appends source video frames to mRawSegment. We only append the source chunk
|
||||
* if it is unique to mLastChunk. Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
nsresult AppendVideoSegment(const VideoSegment& aSegment);
|
||||
|
||||
/**
|
||||
* Tells the video track encoder that we've reached the end of source stream,
|
||||
* and wakes up mReentrantMonitor if encoder is waiting for more track data.
|
||||
* Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
virtual void NotifyEndOfStream() MOZ_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Create a buffer of black image in format of YUV:420. Called on the worker
|
||||
* thread.
|
||||
*/
|
||||
void CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer);
|
||||
|
||||
/**
|
||||
* The width of source video frame, ceiled if the source width is odd.
|
||||
*/
|
||||
int mFrameWidth;
|
||||
|
||||
/**
|
||||
* The height of source video frame, ceiled if the source height is odd.
|
||||
*/
|
||||
int mFrameHeight;
|
||||
|
||||
/**
|
||||
* The track rate of source video.
|
||||
*/
|
||||
TrackRate mTrackRate;
|
||||
|
||||
/**
|
||||
* The total duration of frames in encoded video in TrackTicks, kept track of
|
||||
* in subclasses.
|
||||
*/
|
||||
TrackTicks mTotalFrameDuration;
|
||||
|
||||
/**
|
||||
* The last unique frame we've sent to track encoder, kept track of in
|
||||
* subclasses.
|
||||
*/
|
||||
VideoFrame mLastFrame;
|
||||
|
||||
/**
|
||||
* A segment queue of audio track data, protected by mReentrantMonitor.
|
||||
*/
|
||||
VideoSegment mRawSegment;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -28,15 +28,19 @@ function debug(aMsg) {
|
||||
//dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
|
||||
}
|
||||
|
||||
const DIRECTORY_NAME = "webappsDir";
|
||||
|
||||
// The files will be stored on DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR
|
||||
// SINGLE_VARIANT_CONF_FILE will be stored on SINGLE_VARIANT_SOURCE_DIR
|
||||
// Apps will be stored on a app per directory basis, hanging from
|
||||
// Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR
|
||||
// preference.
|
||||
// if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as
|
||||
// single variant source) the value of
|
||||
// DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead.
|
||||
// SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source.
|
||||
// Apps will be stored on an app per directory basis, hanging from
|
||||
// SINGLE_VARIANT_SOURCE_DIR
|
||||
const DIRECTORY_NAME = "webappsDir";
|
||||
const SINGLE_VARIANT_SOURCE_DIR = "svoperapps";
|
||||
const SINGLE_VARIANT_CONF_FILE = "singlevariantconf.json";
|
||||
const PREF_FIRST_RUN_WITH_SIM = "dom.webapps.firstRunWithSIM";
|
||||
const PREF_SINGLE_VARIANT_DIR = "dom.mozApps.single_variant_sourcedir";
|
||||
const METADATA = "metadata.json";
|
||||
const UPDATEMANIFEST = "update.webapp";
|
||||
const MANIFEST = "manifest.webapp";
|
||||
@ -54,6 +58,9 @@ function isFirstRunWithSIM() {
|
||||
}
|
||||
|
||||
#ifdef MOZ_B2G_RIL
|
||||
let File = OS.File;
|
||||
let Path = OS.Path;
|
||||
|
||||
let iccListener = {
|
||||
notifyStkCommand: function() {},
|
||||
|
||||
@ -88,32 +95,120 @@ this.OperatorAppsRegistry = {
|
||||
#ifdef MOZ_B2G_RIL
|
||||
if (isFirstRunWithSIM()) {
|
||||
debug("First Run with SIM");
|
||||
// TODO: Bug 927709 - OperatorApps for multi-sim
|
||||
// In Multi-sim, there is more than one client in iccProvider. Each
|
||||
// client represents a icc service. To maintain the backward compatibility
|
||||
// with single sim, we always use client 0 for now. Adding support for
|
||||
// multiple sim will be addressed in bug 927709, if needed.
|
||||
let clientId = 0;
|
||||
let iccInfo = iccProvider.getIccInfo(clientId);
|
||||
let mcc = 0;
|
||||
let mnc = 0;
|
||||
if (iccInfo && iccInfo.mcc) {
|
||||
mcc = iccInfo.mcc;
|
||||
}
|
||||
if (iccInfo && iccInfo.mnc) {
|
||||
mnc = iccInfo.mnc;
|
||||
}
|
||||
if (mcc && mnc) {
|
||||
this._installOperatorApps(mcc, mnc);
|
||||
} else {
|
||||
iccProvider.registerIccMsg(clientId, iccListener);
|
||||
}
|
||||
Task.spawn(function() {
|
||||
try {
|
||||
yield this._initializeSourceDir();
|
||||
// TODO: Bug 927709 - OperatorApps for multi-sim
|
||||
// In Multi-sim, there is more than one client in iccProvider. Each
|
||||
// client represents a icc service. To maintain the backward
|
||||
// compatibility with single sim, we always use client 0 for now.
|
||||
// Adding support for multiple sim will be addressed in bug 927709, if
|
||||
// needed.
|
||||
let clientId = 0;
|
||||
let iccInfo = iccProvider.getIccInfo(clientId);
|
||||
let mcc = 0;
|
||||
let mnc = 0;
|
||||
if (iccInfo && iccInfo.mcc) {
|
||||
mcc = iccInfo.mcc;
|
||||
}
|
||||
if (iccInfo && iccInfo.mnc) {
|
||||
mnc = iccInfo.mnc;
|
||||
}
|
||||
if (mcc && mnc) {
|
||||
this._installOperatorApps(mcc, mnc);
|
||||
} else {
|
||||
iccProvider.registerIccMsg(clientId, iccListener);
|
||||
}
|
||||
} catch (e) {
|
||||
debug("Error Initializing OperatorApps. " + e);
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
debug("No First Run with SIM");
|
||||
}
|
||||
#endif
|
||||
},
|
||||
|
||||
_copyDirectory: function(aOrg, aDst) {
|
||||
debug("copying " + aOrg + " to " + aDst);
|
||||
return aDst && Task.spawn(function() {
|
||||
try {
|
||||
let orgInfo = yield File.stat(aOrg);
|
||||
if (!orgInfo.isDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dirDstExists = yield File.exists(aDst);
|
||||
if (!dirDstExists) {
|
||||
yield File.makeDir(aDst);
|
||||
}
|
||||
let iterator = new File.DirectoryIterator(aOrg);
|
||||
if (!iterator) {
|
||||
debug("No iterator over: " + aOrg);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
while (true) {
|
||||
let entry;
|
||||
try {
|
||||
entry = yield iterator.next();
|
||||
} catch (ex if ex == StopIteration) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!entry.isDir) {
|
||||
yield File.copy(entry.path, Path.join(aDst, entry.name));
|
||||
} else {
|
||||
yield this._copyDirectory(entry.path,
|
||||
Path.join(aDst, entry.name));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
iterator.close();
|
||||
}
|
||||
} catch (e) {
|
||||
debug("Error copying " + aOrg + " to " + aDst + ". " + e);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_initializeSourceDir: function() {
|
||||
return Task.spawn(function() {
|
||||
let svFinalDirName;
|
||||
try {
|
||||
svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR);
|
||||
} catch(e) {
|
||||
debug ("Error getting pref. " + e);
|
||||
this.appsDir = FileUtils.getFile(DIRECTORY_NAME,
|
||||
[SINGLE_VARIANT_SOURCE_DIR]).path;
|
||||
return;
|
||||
}
|
||||
// If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return
|
||||
// PREF_SINGLE_VARIANT_DIR as sourceDir, else go to
|
||||
// DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
|
||||
// configuration file) to PREF_SINGLE_VARIANT_DIR and return
|
||||
// PREF_SINGLE_VARIANT_DIR as sourceDir.
|
||||
let existsDir = yield File.exists(svFinalDirName);
|
||||
if (!existsDir) {
|
||||
yield File.makeDir(svFinalDirName, {ignoreExisting: true});
|
||||
}
|
||||
|
||||
let existsSvIndex = yield File.exists(Path.join(svFinalDirName,
|
||||
SINGLE_VARIANT_CONF_FILE));
|
||||
if (!existsSvIndex) {
|
||||
let svSourceDirName = FileUtils.getFile(DIRECTORY_NAME,
|
||||
[SINGLE_VARIANT_SOURCE_DIR]).path;
|
||||
yield this._copyDirectory(svSourceDirName, svFinalDirName);
|
||||
debug("removing directory:" + svSourceDirName);
|
||||
File.removeDir(svSourceDirName, {
|
||||
ignoreAbsent: true,
|
||||
ignorePermissions: true
|
||||
});
|
||||
}
|
||||
this.appsDir = svFinalDirName;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
set appsDir(aDir) {
|
||||
debug("appsDir SET: " + aDir);
|
||||
if (aDir) {
|
||||
@ -126,10 +221,6 @@ this.OperatorAppsRegistry = {
|
||||
},
|
||||
|
||||
get appsDir() {
|
||||
if (!this._baseDirectory) {
|
||||
this._baseDirectory = FileUtils.getFile(DIRECTORY_NAME,
|
||||
[SINGLE_VARIANT_SOURCE_DIR]);
|
||||
}
|
||||
return this._baseDirectory;
|
||||
},
|
||||
|
||||
|
@ -9,6 +9,28 @@ const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
// Possible errors thrown by the signature verifier.
|
||||
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
|
||||
const SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11);
|
||||
|
||||
// We need this to decide if we should accept or not files signed with expired
|
||||
// certificates.
|
||||
function buildIDToTime() {
|
||||
let platformBuildID =
|
||||
Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULAppInfo).platformBuildID;
|
||||
let platformBuildIDDate = new Date();
|
||||
platformBuildIDDate.setUTCFullYear(platformBuildID.substr(0,4),
|
||||
platformBuildID.substr(4,2) - 1,
|
||||
platformBuildID.substr(6,2));
|
||||
platformBuildIDDate.setUTCHours(platformBuildID.substr(8,2),
|
||||
platformBuildID.substr(10,2),
|
||||
platformBuildID.substr(12,2));
|
||||
return platformBuildIDDate.getTime();
|
||||
}
|
||||
|
||||
const PLATFORM_BUILD_ID_TIME = buildIDToTime();
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -38,6 +60,10 @@ function debug(aMsg) {
|
||||
#endif
|
||||
}
|
||||
|
||||
function getNSPRErrorCode(err) {
|
||||
return -1 * ((err) & 0xffff);
|
||||
}
|
||||
|
||||
function supportUseCurrentProfile() {
|
||||
return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
|
||||
}
|
||||
@ -2485,6 +2511,10 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
|
||||
// Check if it's a local file install (we've downloaded/sideloaded the
|
||||
// package already or it did exist on the build).
|
||||
// Note that this variable also controls whether files signed with expired
|
||||
// certificates are accepted or not. If isLocalFileInstall is true and the
|
||||
// device date is earlier than the build generation date, then the signature
|
||||
// will be accepted even if the certificate is expired.
|
||||
let isLocalFileInstall =
|
||||
Services.io.extractScheme(fullPackagePath) === 'file';
|
||||
|
||||
@ -2837,7 +2867,8 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
let zipReader, isSigned, newManifest;
|
||||
|
||||
try {
|
||||
[zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp);
|
||||
[zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp,
|
||||
aIsLocalFileInstall);
|
||||
newManifest = yield this._readPackage(aOldApp, aNewApp,
|
||||
aIsLocalFileInstall, aIsUpdate, aManifest, aRequestChannel,
|
||||
aHash, zipReader, isSigned);
|
||||
@ -2868,7 +2899,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_openPackage: function(aZipFile, aApp) {
|
||||
_openPackage: function(aZipFile, aApp, aIsLocalFileInstall) {
|
||||
return Task.spawn((function*() {
|
||||
let certDb;
|
||||
try {
|
||||
@ -2883,16 +2914,30 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
|
||||
let [result, zipReader] = yield this._openSignedPackage(aZipFile, certDb);
|
||||
|
||||
// We cannot really know if the system date is correct or
|
||||
// not. What we can know is if it's after the build date or not,
|
||||
// and assume the build date is correct (which we cannot
|
||||
// really know either).
|
||||
let isLaterThanBuildTime = Date.now() > PLATFORM_BUILD_ID_TIME;
|
||||
|
||||
let isSigned;
|
||||
|
||||
if (Components.isSuccessCode(result)) {
|
||||
isSigned = true;
|
||||
} else if (result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
||||
throw "APP_PACKAGE_CORRUPTED";
|
||||
} else if (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
|
||||
} else if ((!aIsLocalFileInstall || isLaterThanBuildTime) &&
|
||||
(result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)) {
|
||||
throw "INVALID_SIGNATURE";
|
||||
} else {
|
||||
isSigned = false;
|
||||
// If it's a localFileInstall and the validation failed
|
||||
// because of a expired certificate, just assume it was valid
|
||||
// and that the error occurred because the system time has not
|
||||
// been set yet.
|
||||
isSigned = (aIsLocalFileInstall &&
|
||||
(getNSPRErrorCode(result) ==
|
||||
SEC_ERROR_EXPIRED_CERTIFICATE));
|
||||
|
||||
zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
||||
.createInstance(Ci.nsIZipReader);
|
||||
zipReader.open(aZipFile);
|
||||
|
1526
dom/bluetooth/bluedroid/BluetoothOppManager.cpp
Normal file
1526
dom/bluetooth/bluedroid/BluetoothOppManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
dom/bluetooth/bluedroid/BluetoothOppManager.h
Normal file
226
dom/bluetooth/bluedroid/BluetoothOppManager.h
Normal file
@ -0,0 +1,226 @@
|
||||
/* -*- 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_bluetoothoppmanager_h__
|
||||
#define mozilla_dom_bluetooth_bluetoothoppmanager_h__
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
#include "BluetoothProfileManagerBase.h"
|
||||
#include "BluetoothSocketObserver.h"
|
||||
#include "DeviceStorage.h"
|
||||
#include "mozilla/dom/ipc/Blob.h"
|
||||
#include "mozilla/ipc/UnixSocket.h"
|
||||
#include "nsCOMArray.h"
|
||||
|
||||
class nsIOutputStream;
|
||||
class nsIInputStream;
|
||||
class nsIVolumeMountLock;
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
class BluetoothSocket;
|
||||
class ObexHeaderSet;
|
||||
class SendFileBatch;
|
||||
|
||||
class BluetoothOppManager : public BluetoothSocketObserver
|
||||
, public BluetoothProfileManagerBase
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
BT_DECL_PROFILE_MGR_BASE
|
||||
virtual void GetName(nsACString& aName)
|
||||
{
|
||||
aName.AssignLiteral("OPP");
|
||||
}
|
||||
|
||||
/*
|
||||
* Channel of reserved services are fixed values, please check
|
||||
* function add_reserved_service_records() in
|
||||
* external/bluetooth/bluez/src/adapter.c for more information.
|
||||
*/
|
||||
static const int DEFAULT_OPP_CHANNEL = 10;
|
||||
static const int MAX_PACKET_LENGTH = 0xFFFE;
|
||||
|
||||
~BluetoothOppManager();
|
||||
static BluetoothOppManager* Get();
|
||||
void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
|
||||
void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
|
||||
|
||||
bool Listen();
|
||||
|
||||
bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
|
||||
bool StopSendingFile();
|
||||
bool ConfirmReceivingFile(bool aConfirm);
|
||||
|
||||
void SendConnectRequest();
|
||||
void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
|
||||
void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
|
||||
void SendPutFinalRequest();
|
||||
void SendDisconnectRequest();
|
||||
|
||||
void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
|
||||
bool ExtractBlobHeaders();
|
||||
void CheckPutFinal(uint32_t aNumRead);
|
||||
|
||||
// The following functions are inherited from BluetoothSocketObserver
|
||||
void ReceiveSocketData(
|
||||
BluetoothSocket* aSocket,
|
||||
nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
|
||||
virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
BluetoothOppManager();
|
||||
bool Init();
|
||||
void HandleShutdown();
|
||||
|
||||
void StartFileTransfer();
|
||||
void StartSendingNextFile();
|
||||
void FileTransferComplete();
|
||||
void UpdateProgress();
|
||||
void ReceivingFileConfirmation();
|
||||
bool CreateFile();
|
||||
bool WriteToFile(const uint8_t* aData, int aDataLength);
|
||||
void DeleteReceivedFile();
|
||||
void ReplyToConnect();
|
||||
void ReplyToDisconnectOrAbort();
|
||||
void ReplyToPut(bool aFinal, bool aContinue);
|
||||
void ReplyError(uint8_t aError);
|
||||
void AfterOppConnected();
|
||||
void AfterFirstPut();
|
||||
void AfterOppDisconnected();
|
||||
void ValidateFileName();
|
||||
bool IsReservedChar(PRUnichar c);
|
||||
void ClearQueue();
|
||||
void RetrieveSentFileName();
|
||||
void NotifyAboutFileChange();
|
||||
bool AcquireSdcardMountLock();
|
||||
void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
|
||||
void AppendBlobToSend(const nsAString& aDeviceAddress, BlobParent* aActor);
|
||||
void DiscardBlobsToSend();
|
||||
bool ProcessNextBatch();
|
||||
void ConnectInternal(const nsAString& aDeviceAddress);
|
||||
|
||||
/**
|
||||
* Usually we won't get a full PUT packet in one operation, which means that
|
||||
* a packet may be devided into several parts and BluetoothOppManager should
|
||||
* be in charge of assembling.
|
||||
*
|
||||
* @return true if a packet has been fully received.
|
||||
* false if the received length exceeds/not reaches the expected
|
||||
* length.
|
||||
*/
|
||||
bool ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage);
|
||||
|
||||
/**
|
||||
* OBEX session status.
|
||||
* Set when OBEX session is established.
|
||||
*/
|
||||
bool mConnected;
|
||||
nsString mDeviceAddress;
|
||||
|
||||
/**
|
||||
* Remote information
|
||||
*/
|
||||
uint8_t mRemoteObexVersion;
|
||||
uint8_t mRemoteConnectionFlags;
|
||||
int mRemoteMaxPacketLength;
|
||||
|
||||
/**
|
||||
* For sending files, we decide our next action based on current command and
|
||||
* previous one.
|
||||
* For receiving files, we don't need previous command and it is set to 0
|
||||
* as a default value.
|
||||
*/
|
||||
int mLastCommand;
|
||||
|
||||
int mPacketLength;
|
||||
int mPacketReceivedLength;
|
||||
int mBodySegmentLength;
|
||||
int mUpdateProgressCounter;
|
||||
|
||||
/**
|
||||
* When it is true and the target service on target device couldn't be found,
|
||||
* refreshing SDP records is necessary.
|
||||
*/
|
||||
bool mNeedsUpdatingSdpRecords;
|
||||
|
||||
/**
|
||||
* Set when StopSendingFile() is called.
|
||||
*/
|
||||
bool mAbortFlag;
|
||||
|
||||
/**
|
||||
* Set when receiving the first PUT packet of a new file
|
||||
*/
|
||||
bool mNewFileFlag;
|
||||
|
||||
/**
|
||||
* Set when receiving a PutFinal packet
|
||||
*/
|
||||
bool mPutFinalFlag;
|
||||
|
||||
/**
|
||||
* Set when FileTransferComplete() is called
|
||||
*/
|
||||
bool mSendTransferCompleteFlag;
|
||||
|
||||
/**
|
||||
* Set when a transfer is successfully completed.
|
||||
*/
|
||||
bool mSuccessFlag;
|
||||
|
||||
/**
|
||||
* True: Receive file (Server)
|
||||
* False: Send file (Client)
|
||||
*/
|
||||
bool mIsServer;
|
||||
|
||||
/**
|
||||
* Set when receiving the first PUT packet and wait for
|
||||
* ConfirmReceivingFile() to be called.
|
||||
*/
|
||||
bool mWaitingForConfirmationFlag;
|
||||
|
||||
nsString mFileName;
|
||||
nsString mContentType;
|
||||
uint32_t mFileLength;
|
||||
uint32_t mSentFileLength;
|
||||
bool mWaitingToSendPutFinal;
|
||||
|
||||
nsAutoArrayPtr<uint8_t> mBodySegment;
|
||||
nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
|
||||
|
||||
int mCurrentBlobIndex;
|
||||
nsCOMPtr<nsIDOMBlob> mBlob;
|
||||
nsTArray<SendFileBatch> mBatches;
|
||||
|
||||
/**
|
||||
* A seperate member thread is required because our read calls can block
|
||||
* execution, which is not allowed to happen on the IOThread.
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mReadFileThread;
|
||||
nsCOMPtr<nsIOutputStream> mOutputStream;
|
||||
nsCOMPtr<nsIInputStream> mInputStream;
|
||||
nsCOMPtr<nsIVolumeMountLock> mMountLock;
|
||||
nsRefPtr<DeviceStorageFile> mDsFile;
|
||||
|
||||
// If a connection has been established, mSocket will be the socket
|
||||
// communicating with the remote socket. We maintain the invariant that if
|
||||
// mSocket is non-null, mServerSocket must be null (and vice versa).
|
||||
nsRefPtr<BluetoothSocket> mSocket;
|
||||
|
||||
// Server sockets. Once an inbound connection is established, it will hand
|
||||
// over the ownership to mSocket, and get a new server socket while Listen()
|
||||
// is called.
|
||||
nsRefPtr<BluetoothSocket> mServerSocket;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
#endif
|
734
dom/bluetooth/bluedroid/BluetoothSocket.cpp
Normal file
734
dom/bluetooth/bluedroid/BluetoothSocket.cpp
Normal file
@ -0,0 +1,734 @@
|
||||
/* -*- 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 "BluetoothSocket.h"
|
||||
|
||||
#include <hardware/bluetooth.h>
|
||||
#include <hardware/bt_sock.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "base/message_loop.h"
|
||||
#include "BluetoothServiceBluedroid.h"
|
||||
#include "BluetoothSocketObserver.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#define FIRST_SOCKET_INFO_MSG_LENGTH 4
|
||||
#define TOTAL_SOCKET_INFO_LENGTH 20
|
||||
|
||||
using namespace mozilla::ipc;
|
||||
USING_BLUETOOTH_NAMESPACE
|
||||
|
||||
static const size_t MAX_READ_SIZE = 1 << 16;
|
||||
static const uint8_t UUID_OBEX_OBJECT_PUSH[] = {
|
||||
0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00,
|
||||
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
||||
};
|
||||
static const btsock_interface_t* sBluetoothSocketInterface = nullptr;
|
||||
|
||||
// helper functions
|
||||
static bool
|
||||
EnsureBluetoothSocketHalLoad()
|
||||
{
|
||||
if (sBluetoothSocketInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bt_interface_t* btInf = GetBluetoothInterface();
|
||||
NS_ENSURE_TRUE(btInf, false);
|
||||
|
||||
sBluetoothSocketInterface =
|
||||
(btsock_interface_t *) btInf->get_profile_interface(BT_PROFILE_SOCKETS_ID);
|
||||
NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int16_t
|
||||
ReadInt16(const uint8_t* aData, size_t* aOffset)
|
||||
{
|
||||
int16_t value = (aData[*aOffset + 1] << 8) | aData[*aOffset];
|
||||
|
||||
*aOffset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
ReadInt32(const uint8_t* aData, size_t* aOffset)
|
||||
{
|
||||
int32_t value = (aData[*aOffset + 3] << 24) |
|
||||
(aData[*aOffset + 2] << 16) |
|
||||
(aData[*aOffset + 1] << 8) |
|
||||
aData[*aOffset];
|
||||
*aOffset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void
|
||||
ReadBdAddress(const uint8_t* aData, size_t* aOffset, nsAString& aDeviceAddress)
|
||||
{
|
||||
char bdstr[18];
|
||||
sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
aData[*aOffset], aData[*aOffset + 1], aData[*aOffset + 2],
|
||||
aData[*aOffset + 3], aData[*aOffset + 4], aData[*aOffset + 5]);
|
||||
|
||||
aDeviceAddress.AssignLiteral(bdstr);
|
||||
*aOffset += 6;
|
||||
}
|
||||
|
||||
class mozilla::dom::bluetooth::DroidSocketImpl
|
||||
: public MessageLoopForIO::Watcher
|
||||
{
|
||||
public:
|
||||
DroidSocketImpl(BluetoothSocket* aConsumer, int aFd)
|
||||
: mConsumer(aConsumer)
|
||||
, mIOLoop(nullptr)
|
||||
, mFd(aFd)
|
||||
, mShuttingDownOnIOThread(false)
|
||||
{
|
||||
}
|
||||
|
||||
~DroidSocketImpl()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
void QueueWriteData(UnixSocketRawData* aData)
|
||||
{
|
||||
mOutgoingQ.AppendElement(aData);
|
||||
OnFileCanWriteWithoutBlocking(mFd);
|
||||
}
|
||||
|
||||
bool IsShutdownOnMainThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mConsumer == nullptr;
|
||||
}
|
||||
|
||||
bool IsShutdownOnIOThread()
|
||||
{
|
||||
return mShuttingDownOnIOThread;
|
||||
}
|
||||
|
||||
void ShutdownOnMainThread()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsShutdownOnMainThread());
|
||||
mConsumer = nullptr;
|
||||
}
|
||||
|
||||
void ShutdownOnIOThread()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
|
||||
mReadWatcher.StopWatchingFileDescriptor();
|
||||
mWriteWatcher.StopWatchingFileDescriptor();
|
||||
|
||||
mShuttingDownOnIOThread = true;
|
||||
}
|
||||
|
||||
void SetUpIO(bool aWrite)
|
||||
{
|
||||
MOZ_ASSERT(!mIOLoop);
|
||||
MOZ_ASSERT(mFd >= 0);
|
||||
mIOLoop = MessageLoopForIO::current();
|
||||
|
||||
// Set up a read watch
|
||||
mIOLoop->WatchFileDescriptor(mFd,
|
||||
true,
|
||||
MessageLoopForIO::WATCH_READ,
|
||||
&mReadWatcher,
|
||||
this);
|
||||
|
||||
if (aWrite) {
|
||||
// Set up a write watch
|
||||
mIOLoop->WatchFileDescriptor(mFd.get(),
|
||||
false,
|
||||
MessageLoopForIO::WATCH_WRITE,
|
||||
&mWriteWatcher,
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectClientFd()
|
||||
{
|
||||
// Stop current read watch
|
||||
mReadWatcher.StopWatchingFileDescriptor();
|
||||
mIOLoop = nullptr;
|
||||
|
||||
// Restart read & write watch on client fd
|
||||
SetUpIO(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer pointer. Non-thread safe RefPtr, so should only be manipulated
|
||||
* directly from main thread. All non-main-thread accesses should happen with
|
||||
* mImpl as container.
|
||||
*/
|
||||
RefPtr<BluetoothSocket> mConsumer;
|
||||
|
||||
private:
|
||||
/**
|
||||
* libevent triggered functions that reads data from socket when available and
|
||||
* guarenteed non-blocking. Only to be called on IO thread.
|
||||
*
|
||||
* @param aFd [in] File descriptor to read from
|
||||
*/
|
||||
virtual void OnFileCanReadWithoutBlocking(int aFd);
|
||||
|
||||
/**
|
||||
* libevent or developer triggered functions that writes data to socket when
|
||||
* available and guarenteed non-blocking. Only to be called on IO thread.
|
||||
*
|
||||
* @param aFd [in] File descriptor to read from
|
||||
*/
|
||||
virtual void OnFileCanWriteWithoutBlocking(int aFd);
|
||||
|
||||
/**
|
||||
* Read message to get data and client fd wrapped in message header
|
||||
*
|
||||
* @param aFd [in] File descriptor to read message from
|
||||
* @param aBuffer [out] Data buffer read
|
||||
* @param aLength [out] Number of bytes read
|
||||
*/
|
||||
ssize_t ReadMsg(int aFd, void *aBuffer, size_t aLength);
|
||||
|
||||
/**
|
||||
* IO Loop pointer. Must be initalized and called from IO thread only.
|
||||
*/
|
||||
MessageLoopForIO* mIOLoop;
|
||||
|
||||
/**
|
||||
* Raw data queue. Must be pushed/popped from IO thread only.
|
||||
*/
|
||||
typedef nsTArray<UnixSocketRawData* > UnixSocketRawDataQueue;
|
||||
UnixSocketRawDataQueue mOutgoingQ;
|
||||
|
||||
/**
|
||||
* Read watcher for libevent. Only to be accessed on IO Thread.
|
||||
*/
|
||||
MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
|
||||
|
||||
/**
|
||||
* Write watcher for libevent. Only to be accessed on IO Thread.
|
||||
*/
|
||||
MessageLoopForIO::FileDescriptorWatcher mWriteWatcher;
|
||||
|
||||
/**
|
||||
* File descriptor to read from/write to. Connection happens on user provided
|
||||
* thread. Read/write/close happens on IO thread.
|
||||
*/
|
||||
mozilla::ScopedClose mFd;
|
||||
|
||||
/**
|
||||
* If true, do not requeue whatever task we're running
|
||||
*/
|
||||
bool mShuttingDownOnIOThread;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class DeleteInstanceRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DeleteInstanceRunnable(T* aInstance)
|
||||
: mInstance(aInstance)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
delete mInstance;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
T* mInstance;
|
||||
};
|
||||
|
||||
class RequestClosingSocketTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
RequestClosingSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl)
|
||||
{
|
||||
MOZ_ASSERT(aImpl);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mImpl->IsShutdownOnMainThread()) {
|
||||
NS_WARNING("CloseSocket has already been called!");
|
||||
// Since we've already explicitly closed and the close happened before
|
||||
// this, this isn't really an error. Since we've warned, return OK.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Start from here, same handling flow as calling CloseSocket() from
|
||||
// upper layer
|
||||
mImpl->mConsumer->CloseDroidSocket();
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
};
|
||||
|
||||
class ShutdownSocketTask : public Task {
|
||||
virtual void Run()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
// At this point, there should be no new events on the IO thread after this
|
||||
// one with the possible exception of a SocketAcceptTask that
|
||||
// ShutdownOnIOThread will cancel for us. We are now fully shut down, so we
|
||||
// can send a message to the main thread that will delete mImpl safely knowing
|
||||
// that no more tasks reference it.
|
||||
mImpl->ShutdownOnIOThread();
|
||||
|
||||
nsRefPtr<nsIRunnable> t(new DeleteInstanceRunnable<
|
||||
mozilla::dom::bluetooth::DroidSocketImpl>(mImpl));
|
||||
nsresult rv = NS_DispatchToMainThread(t);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
}
|
||||
|
||||
DroidSocketImpl* mImpl;
|
||||
|
||||
public:
|
||||
ShutdownSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { }
|
||||
};
|
||||
|
||||
class SocketReceiveTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
SocketReceiveTask(DroidSocketImpl* aImpl, UnixSocketRawData* aData) :
|
||||
mImpl(aImpl),
|
||||
mRawData(aData)
|
||||
{
|
||||
MOZ_ASSERT(aImpl);
|
||||
MOZ_ASSERT(aData);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mImpl->IsShutdownOnMainThread()) {
|
||||
NS_WARNING("mConsumer is null, aborting receive!");
|
||||
// Since we've already explicitly closed and the close happened before
|
||||
// this, this isn't really an error. Since we've warned, return OK.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mImpl->mConsumer);
|
||||
mImpl->mConsumer->ReceiveSocketData(mRawData);
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
DroidSocketImpl* mImpl;
|
||||
nsAutoPtr<UnixSocketRawData> mRawData;
|
||||
};
|
||||
|
||||
class SocketSendTask : public Task
|
||||
{
|
||||
public:
|
||||
SocketSendTask(BluetoothSocket* aConsumer, DroidSocketImpl* aImpl,
|
||||
UnixSocketRawData* aData)
|
||||
: mConsumer(aConsumer),
|
||||
mImpl(aImpl),
|
||||
mData(aData)
|
||||
{
|
||||
MOZ_ASSERT(aConsumer);
|
||||
MOZ_ASSERT(aImpl);
|
||||
MOZ_ASSERT(aData);
|
||||
}
|
||||
|
||||
void
|
||||
Run()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mImpl->IsShutdownOnIOThread());
|
||||
|
||||
mImpl->QueueWriteData(mData);
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothSocket> mConsumer;
|
||||
DroidSocketImpl* mImpl;
|
||||
UnixSocketRawData* mData;
|
||||
};
|
||||
|
||||
class SocketSetUpIOTask : public Task
|
||||
{
|
||||
virtual void Run()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
mImpl->SetUpIO(mWrite);
|
||||
}
|
||||
|
||||
DroidSocketImpl* mImpl;
|
||||
bool mWrite;
|
||||
public:
|
||||
SocketSetUpIOTask(DroidSocketImpl* aImpl, bool aWrite)
|
||||
: mImpl(aImpl), mWrite(aWrite) { }
|
||||
};
|
||||
|
||||
class SocketConnectClientFdTask : public Task
|
||||
{
|
||||
virtual void Run()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
mImpl->ConnectClientFd();
|
||||
}
|
||||
|
||||
DroidSocketImpl* mImpl;
|
||||
public:
|
||||
SocketConnectClientFdTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { }
|
||||
};
|
||||
|
||||
ssize_t
|
||||
DroidSocketImpl::ReadMsg(int aFd, void *aBuffer, size_t aLength)
|
||||
{
|
||||
ssize_t ret;
|
||||
struct msghdr msg;
|
||||
struct iovec iv;
|
||||
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&iv, 0, sizeof(iv));
|
||||
|
||||
iv.iov_base = (unsigned char *)aBuffer;
|
||||
iv.iov_len = aLength;
|
||||
|
||||
msg.msg_iov = &iv;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
ret = recvmsg(mFd.get(), &msg, MSG_NOSIGNAL);
|
||||
if (ret < 0 && errno == EPIPE) {
|
||||
// Treat this as an end of stream
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_ENSURE_FALSE(ret < 0, -1);
|
||||
NS_ENSURE_FALSE(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE), -1);
|
||||
|
||||
// Extract client fd from message header
|
||||
for (struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
|
||||
cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
|
||||
if (cmsgptr->cmsg_level != SOL_SOCKET) {
|
||||
continue;
|
||||
}
|
||||
if (cmsgptr->cmsg_type == SCM_RIGHTS) {
|
||||
int *pDescriptors = (int *)CMSG_DATA(cmsgptr);
|
||||
// Overwrite fd with client fd
|
||||
mFd.reset(pDescriptors[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
|
||||
// Read all of the incoming data.
|
||||
while (true) {
|
||||
nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
|
||||
|
||||
ssize_t ret;
|
||||
if (!mConsumer->IsWaitingForClientFd()) {
|
||||
ret = read(aFd, incoming->mData, incoming->mSize);
|
||||
} else {
|
||||
ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue; // retry system call when interrupted
|
||||
}
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
return; // no data available: return and re-poll
|
||||
}
|
||||
|
||||
BT_WARNING("Cannot read from network");
|
||||
// else fall through to error handling on other errno's
|
||||
}
|
||||
|
||||
// We're done with our descriptors. Ensure that spurious events don't
|
||||
// cause us to end up back here.
|
||||
mReadWatcher.StopWatchingFileDescriptor();
|
||||
mWriteWatcher.StopWatchingFileDescriptor();
|
||||
nsRefPtr<RequestClosingSocketTask> t = new RequestClosingSocketTask(this);
|
||||
NS_DispatchToMainThread(t);
|
||||
return;
|
||||
}
|
||||
|
||||
incoming->mSize = ret;
|
||||
nsRefPtr<SocketReceiveTask> t =
|
||||
new SocketReceiveTask(this, incoming.forget());
|
||||
NS_DispatchToMainThread(t);
|
||||
|
||||
// If ret is less than MAX_READ_SIZE, there's no
|
||||
// more data in the socket for us to read now.
|
||||
if (ret < ssize_t(MAX_READ_SIZE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CRASH("We returned early");
|
||||
}
|
||||
|
||||
void
|
||||
DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mShuttingDownOnIOThread);
|
||||
MOZ_ASSERT(aFd >= 0);
|
||||
|
||||
// Try to write the bytes of mCurrentRilRawData. If all were written, continue.
|
||||
//
|
||||
// Otherwise, save the byte position of the next byte to write
|
||||
// within mCurrentWriteOffset, and request another write when the
|
||||
// system won't block.
|
||||
//
|
||||
while (true) {
|
||||
UnixSocketRawData* data;
|
||||
if (mOutgoingQ.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
data = mOutgoingQ.ElementAt(0);
|
||||
const uint8_t *toWrite;
|
||||
toWrite = data->mData;
|
||||
|
||||
while (data->mCurrentWriteOffset < data->mSize) {
|
||||
ssize_t write_amount = data->mSize - data->mCurrentWriteOffset;
|
||||
ssize_t written;
|
||||
written = write (aFd, toWrite + data->mCurrentWriteOffset,
|
||||
write_amount);
|
||||
if (written > 0) {
|
||||
data->mCurrentWriteOffset += written;
|
||||
}
|
||||
if (written != write_amount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->mCurrentWriteOffset != data->mSize) {
|
||||
MessageLoopForIO::current()->WatchFileDescriptor(
|
||||
aFd,
|
||||
false,
|
||||
MessageLoopForIO::WATCH_WRITE,
|
||||
&mWriteWatcher,
|
||||
this);
|
||||
return;
|
||||
}
|
||||
mOutgoingQ.RemoveElementAt(0);
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
|
||||
BluetoothSocketType aType,
|
||||
bool aAuth,
|
||||
bool aEncrypt)
|
||||
: mObserver(aObserver)
|
||||
, mImpl(nullptr)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
, mReceivedSocketInfoLength(0)
|
||||
{
|
||||
MOZ_ASSERT(aObserver);
|
||||
|
||||
EnsureBluetoothSocketHalLoad();
|
||||
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::CloseDroidSocket()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mImpl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From this point on, we consider mImpl as being deleted.
|
||||
// We sever the relationship here so any future calls to listen or connect
|
||||
// will create a new implementation.
|
||||
mImpl->ShutdownOnMainThread();
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new ShutdownSocketTask(mImpl));
|
||||
mImpl = nullptr;
|
||||
|
||||
OnDisconnect();
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::CreateDroidSocket(int aFd)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ENSURE_FALSE(mImpl, false);
|
||||
|
||||
mImpl = new DroidSocketImpl(this, aFd);
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketSetUpIOTask(mImpl, !mIsServer));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::Connect(const nsAString& aDeviceAddress, int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aDeviceAddress.IsEmpty());
|
||||
NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
|
||||
|
||||
bt_bdaddr_t remoteBdAddress;
|
||||
StringToBdAddressType(aDeviceAddress, &remoteBdAddress);
|
||||
|
||||
// TODO: uuid as argument
|
||||
int fd;
|
||||
NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
|
||||
sBluetoothSocketInterface->connect((bt_bdaddr_t *) &remoteBdAddress,
|
||||
(btsock_type_t) BTSOCK_RFCOMM,
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
aChannel, &fd, (mAuth << 1) | mEncrypt),
|
||||
false);
|
||||
NS_ENSURE_TRUE(fd >= 0, false);
|
||||
|
||||
mIsServer = false;
|
||||
return CreateDroidSocket(fd);
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::Listen(int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
|
||||
|
||||
// TODO: uuid and service name as arguments
|
||||
nsAutoCString serviceName("OBEX Object Push");
|
||||
int fd;
|
||||
NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
|
||||
sBluetoothSocketInterface->listen((btsock_type_t) BTSOCK_RFCOMM,
|
||||
serviceName.get(),
|
||||
UUID_OBEX_OBJECT_PUSH,
|
||||
aChannel, &fd, (mAuth << 1) | mEncrypt),
|
||||
false);
|
||||
NS_ENSURE_TRUE(fd >= 0, false);
|
||||
|
||||
mIsServer = true;
|
||||
return CreateDroidSocket(fd);
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ENSURE_TRUE(mImpl, false);
|
||||
|
||||
MOZ_ASSERT(!mImpl->IsShutdownOnMainThread());
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketSendTask(this, mImpl, aData));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::IsWaitingForClientFd()
|
||||
{
|
||||
return (mIsServer &&
|
||||
mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH);
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
|
||||
{
|
||||
/**
|
||||
* 2 socket info messages (20 bytes) to receive at the beginning:
|
||||
* - 1st message: [channel:4]
|
||||
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
|
||||
*/
|
||||
if (mReceivedSocketInfoLength >= TOTAL_SOCKET_INFO_LENGTH) {
|
||||
// We've got both socket info messages
|
||||
return false;
|
||||
}
|
||||
mReceivedSocketInfoLength += aMessage->mSize;
|
||||
|
||||
size_t offset = 0;
|
||||
if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) {
|
||||
// 1st message: [channel:4]
|
||||
int32_t channel = ReadInt32(aMessage->mData, &offset);
|
||||
|
||||
BT_LOGR("%s: channel %d", __FUNCTION__, channel);
|
||||
} else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) {
|
||||
// 2nd message: [size:2][bd address:6][channel:4][connection status:4]
|
||||
int16_t size = ReadInt16(aMessage->mData, &offset);
|
||||
ReadBdAddress(aMessage->mData, &offset, mDeviceAddress);
|
||||
int32_t channel = ReadInt32(aMessage->mData, &offset);
|
||||
int32_t connectionStatus = ReadInt32(aMessage->mData, &offset);
|
||||
|
||||
BT_LOGR("%s: size %d channel %d remote addr %s status %d", __FUNCTION__,
|
||||
size, channel, NS_ConvertUTF16toUTF8(mDeviceAddress).get(), connectionStatus);
|
||||
|
||||
if (connectionStatus != 0) {
|
||||
OnConnectError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mIsServer) {
|
||||
// Connect client fd on IO thread
|
||||
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
|
||||
new SocketConnectClientFdTask(mImpl));
|
||||
}
|
||||
OnConnectSuccess();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
|
||||
{
|
||||
if (ReceiveSocketInfo(aMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->ReceiveSocketData(this, aMessage);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnConnectSuccess()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketConnectSuccess(this);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnConnectError()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketConnectError(this);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnDisconnect()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketDisconnect(this);
|
||||
}
|
||||
|
87
dom/bluetooth/bluedroid/BluetoothSocket.h
Normal file
87
dom/bluetooth/bluedroid/BluetoothSocket.h
Normal file
@ -0,0 +1,87 @@
|
||||
/* -*- 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_BluetoothSocket_h
|
||||
#define mozilla_dom_bluetooth_BluetoothSocket_h
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
#include "mozilla/ipc/UnixSocket.h"
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
class BluetoothSocketObserver;
|
||||
class DroidSocketImpl;
|
||||
|
||||
class BluetoothSocket : public mozilla::ipc::UnixSocketConsumer
|
||||
{
|
||||
public:
|
||||
BluetoothSocket(BluetoothSocketObserver* aObserver,
|
||||
BluetoothSocketType aType,
|
||||
bool aAuth,
|
||||
bool aEncrypt);
|
||||
|
||||
/**
|
||||
* Connect to remote server as a client.
|
||||
*
|
||||
* The steps are as following:
|
||||
* 1) BluetoothSocket acquires fd from bluedroid, and creates
|
||||
* a DroidSocketImpl to watch read/write of the fd.
|
||||
* 2) DroidSocketImpl receives first 2 messages to get socket info.
|
||||
* 3) Obex client session starts.
|
||||
*/
|
||||
bool Connect(const nsAString& aDeviceAddress, int aChannel);
|
||||
|
||||
/**
|
||||
* Listen to incoming connection as a server.
|
||||
*
|
||||
* The steps are as following:
|
||||
* 1) BluetoothSocket acquires fd from bluedroid, and creates
|
||||
* a DroidSocketImpl to watch read of the fd. DroidSocketImpl
|
||||
* receives the 1st message immediately.
|
||||
* 2) When there's incoming connection, DroidSocketImpl receives
|
||||
* 2nd message to get socket info and client fd.
|
||||
* 3) DroidSocketImpl stops watching read of original fd and
|
||||
* starts to watch read/write of client fd.
|
||||
* 4) Obex server session starts.
|
||||
*/
|
||||
bool Listen(int aChannel);
|
||||
|
||||
inline void Disconnect()
|
||||
{
|
||||
CloseDroidSocket();
|
||||
}
|
||||
|
||||
virtual void OnConnectSuccess() MOZ_OVERRIDE;
|
||||
virtual void OnConnectError() MOZ_OVERRIDE;
|
||||
virtual void OnDisconnect() MOZ_OVERRIDE;
|
||||
virtual void ReceiveSocketData(
|
||||
nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
|
||||
|
||||
inline void GetAddress(nsAString& aDeviceAddress)
|
||||
{
|
||||
aDeviceAddress = mDeviceAddress;
|
||||
}
|
||||
|
||||
void CloseDroidSocket();
|
||||
bool IsWaitingForClientFd();
|
||||
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
|
||||
|
||||
private:
|
||||
BluetoothSocketObserver* mObserver;
|
||||
DroidSocketImpl* mImpl;
|
||||
nsString mDeviceAddress;
|
||||
bool mAuth;
|
||||
bool mEncrypt;
|
||||
bool mIsServer;
|
||||
int mReceivedSocketInfoLength;
|
||||
|
||||
bool CreateDroidSocket(int aFd);
|
||||
bool ReceiveSocketInfo(nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage);
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
#endif
|
@ -20,8 +20,9 @@
|
||||
|
||||
#include <hardware/hardware.h>
|
||||
|
||||
#include "bluedroid/BluetoothA2dpManager.h"
|
||||
#include "bluedroid/BluetoothHfpManager.h"
|
||||
#include "BluetoothA2dpManager.h"
|
||||
#include "BluetoothHfpManager.h"
|
||||
#include "BluetoothOppManager.h"
|
||||
#include "BluetoothProfileController.h"
|
||||
#include "BluetoothReplyRunnable.h"
|
||||
#include "BluetoothUtils.h"
|
||||
@ -115,6 +116,12 @@ public:
|
||||
bs->AdapterAddedReceived();
|
||||
bs->TryFiringAdapterAdded();
|
||||
|
||||
// Trigger BluetoothOppManager to listen
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
if (!opp || !opp->Listen()) {
|
||||
BT_LOGR("%s: Fail to start BluetoothOppManager listening", __FUNCTION__);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
@ -1290,14 +1297,45 @@ BluetoothServiceBluedroid::SendFile(const nsAString& aDeviceAddress,
|
||||
BlobChild* aBlobChild,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Force to stop discovery, otherwise socket connecting would fail
|
||||
if (!IsReady() || BT_STATUS_SUCCESS != sBtInterface->cancel_discovery()) {
|
||||
NS_NAMED_LITERAL_STRING(errorStr, "Calling cancel_discovery() failed");
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently we only support one device sending one file at a time,
|
||||
// so we don't need aDeviceAddress here because the target device
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
nsAutoString errorStr;
|
||||
if (!opp || !opp->SendFile(aDeviceAddress, aBlobParent)) {
|
||||
errorStr.AssignLiteral("Calling SendFile() failed");
|
||||
}
|
||||
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::StopSendingFile(const nsAString& aDeviceAddress,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Currently we only support one device sending one file at a time,
|
||||
// so we don't need aDeviceAddress here because the target device
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
nsAutoString errorStr;
|
||||
if (!opp || !opp->StopSendingFile()) {
|
||||
errorStr.AssignLiteral("Calling StopSendingFile() failed");
|
||||
}
|
||||
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1305,7 +1343,19 @@ BluetoothServiceBluedroid::ConfirmReceivingFile(
|
||||
const nsAString& aDeviceAddress, bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must be called from main thread!");
|
||||
|
||||
// Currently we only support one device sending one file at a time,
|
||||
// so we don't need aDeviceAddress here because the target device
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
nsAutoString errorStr;
|
||||
if (!opp || !opp->ConfirmReceivingFile(aConfirm)) {
|
||||
errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed");
|
||||
}
|
||||
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -384,6 +384,7 @@ BluetoothHfpManager::Reset()
|
||||
mCMER = false;
|
||||
mConnectScoRequest = false;
|
||||
mSlcConnected = false;
|
||||
mHspConnected = false;
|
||||
mReceiveVgsFlag = false;
|
||||
|
||||
#ifdef MOZ_B2G_RIL
|
||||
@ -1617,6 +1618,7 @@ BluetoothHfpManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
|
||||
mHeadsetSocket = nullptr;
|
||||
} else if (aSocket == mHeadsetSocket) {
|
||||
MOZ_ASSERT(!mSocket);
|
||||
mHspConnected = true;
|
||||
mHeadsetSocket.swap(mSocket);
|
||||
|
||||
mHandsfreeSocket->Disconnect();
|
||||
@ -1802,9 +1804,9 @@ BluetoothHfpManager::ConnectSco(BluetoothReplyRunnable* aRunnable)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure Service Level Connection established before we start to
|
||||
// set up SCO (synchronous connection).
|
||||
if (!mSlcConnected) {
|
||||
// If we are not using HSP, we have to make sure Service Level Connection
|
||||
// established before we start to set up SCO (synchronous connection).
|
||||
if (!mSlcConnected && !mHspConnected) {
|
||||
mConnectScoRequest = true;
|
||||
BT_WARNING("ConnectSco called before Service Level Connection established");
|
||||
return false;
|
||||
|
@ -102,8 +102,9 @@ public:
|
||||
* This function set up a Synchronous Connection (SCO) link for HFP.
|
||||
* Service Level Connection (SLC) should be established before SCO setup
|
||||
* process.
|
||||
* If SLC haven't been established, this function will return false and send a
|
||||
* request to set up SCO ater HfpManager receive AT+CMER.
|
||||
* If SLC haven't been established, this function will return false and
|
||||
* send a request to set up SCO ater HfpManager receive AT+CMER, unless we are
|
||||
* connecting HSP socket rather than HFP socket.
|
||||
*
|
||||
* @param aRunnable Indicate a BluetoothReplyRunnable to execute this
|
||||
* function. The default value is nullpter
|
||||
@ -188,6 +189,7 @@ private:
|
||||
bool mCMER;
|
||||
bool mConnectScoRequest;
|
||||
bool mSlcConnected;
|
||||
bool mHspConnected;
|
||||
#ifdef MOZ_B2G_RIL
|
||||
bool mFirstCKPD;
|
||||
int mNetworkSelectionMode;
|
||||
|
1587
dom/bluetooth/bluez/BluetoothOppManager.cpp
Normal file
1587
dom/bluetooth/bluez/BluetoothOppManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
228
dom/bluetooth/bluez/BluetoothOppManager.h
Normal file
228
dom/bluetooth/bluez/BluetoothOppManager.h
Normal file
@ -0,0 +1,228 @@
|
||||
/* -*- 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_bluetoothoppmanager_h__
|
||||
#define mozilla_dom_bluetooth_bluetoothoppmanager_h__
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
#include "BluetoothProfileManagerBase.h"
|
||||
#include "BluetoothSocketObserver.h"
|
||||
#include "DeviceStorage.h"
|
||||
#include "mozilla/dom/ipc/Blob.h"
|
||||
#include "mozilla/ipc/UnixSocket.h"
|
||||
#include "nsCOMArray.h"
|
||||
|
||||
class nsIOutputStream;
|
||||
class nsIInputStream;
|
||||
class nsIVolumeMountLock;
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
class BluetoothSocket;
|
||||
class ObexHeaderSet;
|
||||
class SendFileBatch;
|
||||
|
||||
class BluetoothOppManager : public BluetoothSocketObserver
|
||||
, public BluetoothProfileManagerBase
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
BT_DECL_PROFILE_MGR_BASE
|
||||
virtual void GetName(nsACString& aName)
|
||||
{
|
||||
aName.AssignLiteral("OPP");
|
||||
}
|
||||
|
||||
/*
|
||||
* Channel of reserved services are fixed values, please check
|
||||
* function add_reserved_service_records() in
|
||||
* external/bluetooth/bluez/src/adapter.c for more information.
|
||||
*/
|
||||
static const int DEFAULT_OPP_CHANNEL = 10;
|
||||
static const int MAX_PACKET_LENGTH = 0xFFFE;
|
||||
|
||||
~BluetoothOppManager();
|
||||
static BluetoothOppManager* Get();
|
||||
void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
|
||||
void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
|
||||
|
||||
bool Listen();
|
||||
|
||||
bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
|
||||
bool StopSendingFile();
|
||||
bool ConfirmReceivingFile(bool aConfirm);
|
||||
|
||||
void SendConnectRequest();
|
||||
void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
|
||||
void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
|
||||
void SendPutFinalRequest();
|
||||
void SendDisconnectRequest();
|
||||
|
||||
void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
|
||||
bool ExtractBlobHeaders();
|
||||
void CheckPutFinal(uint32_t aNumRead);
|
||||
|
||||
// The following functions are inherited from BluetoothSocketObserver
|
||||
void ReceiveSocketData(
|
||||
BluetoothSocket* aSocket,
|
||||
nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
|
||||
virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
BluetoothOppManager();
|
||||
bool Init();
|
||||
void HandleShutdown();
|
||||
|
||||
void StartFileTransfer();
|
||||
void StartSendingNextFile();
|
||||
void FileTransferComplete();
|
||||
void UpdateProgress();
|
||||
void ReceivingFileConfirmation();
|
||||
bool CreateFile();
|
||||
bool WriteToFile(const uint8_t* aData, int aDataLength);
|
||||
void DeleteReceivedFile();
|
||||
void ReplyToConnect();
|
||||
void ReplyToDisconnectOrAbort();
|
||||
void ReplyToPut(bool aFinal, bool aContinue);
|
||||
void ReplyError(uint8_t aError);
|
||||
void AfterOppConnected();
|
||||
void AfterFirstPut();
|
||||
void AfterOppDisconnected();
|
||||
void ValidateFileName();
|
||||
bool IsReservedChar(PRUnichar c);
|
||||
void ClearQueue();
|
||||
void RetrieveSentFileName();
|
||||
void NotifyAboutFileChange();
|
||||
bool AcquireSdcardMountLock();
|
||||
void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
|
||||
void AppendBlobToSend(const nsAString& aDeviceAddress, BlobParent* aActor);
|
||||
void DiscardBlobsToSend();
|
||||
bool ProcessNextBatch();
|
||||
void ConnectInternal(const nsAString& aDeviceAddress);
|
||||
|
||||
/**
|
||||
* Usually we won't get a full PUT packet in one operation, which means that
|
||||
* a packet may be devided into several parts and BluetoothOppManager should
|
||||
* be in charge of assembling.
|
||||
*
|
||||
* @return true if a packet has been fully received.
|
||||
* false if the received length exceeds/not reaches the expected
|
||||
* length.
|
||||
*/
|
||||
bool ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage);
|
||||
|
||||
/**
|
||||
* OBEX session status.
|
||||
* Set when OBEX session is established.
|
||||
*/
|
||||
bool mConnected;
|
||||
nsString mConnectedDeviceAddress;
|
||||
|
||||
/**
|
||||
* Remote information
|
||||
*/
|
||||
uint8_t mRemoteObexVersion;
|
||||
uint8_t mRemoteConnectionFlags;
|
||||
int mRemoteMaxPacketLength;
|
||||
|
||||
/**
|
||||
* For sending files, we decide our next action based on current command and
|
||||
* previous one.
|
||||
* For receiving files, we don't need previous command and it is set to 0
|
||||
* as a default value.
|
||||
*/
|
||||
int mLastCommand;
|
||||
|
||||
int mPacketLength;
|
||||
int mPacketReceivedLength;
|
||||
int mBodySegmentLength;
|
||||
int mUpdateProgressCounter;
|
||||
|
||||
/**
|
||||
* When it is true and the target service on target device couldn't be found,
|
||||
* refreshing SDP records is necessary.
|
||||
*/
|
||||
bool mNeedsUpdatingSdpRecords;
|
||||
|
||||
/**
|
||||
* Set when StopSendingFile() is called.
|
||||
*/
|
||||
bool mAbortFlag;
|
||||
|
||||
/**
|
||||
* Set when receiving the first PUT packet of a new file
|
||||
*/
|
||||
bool mNewFileFlag;
|
||||
|
||||
/**
|
||||
* Set when receiving a PutFinal packet
|
||||
*/
|
||||
bool mPutFinalFlag;
|
||||
|
||||
/**
|
||||
* Set when FileTransferComplete() is called
|
||||
*/
|
||||
bool mSendTransferCompleteFlag;
|
||||
|
||||
/**
|
||||
* Set when a transfer is successfully completed.
|
||||
*/
|
||||
bool mSuccessFlag;
|
||||
|
||||
/**
|
||||
* True: Receive file (Server)
|
||||
* False: Send file (Client)
|
||||
*/
|
||||
bool mIsServer;
|
||||
|
||||
/**
|
||||
* Set when receiving the first PUT packet and wait for
|
||||
* ConfirmReceivingFile() to be called.
|
||||
*/
|
||||
bool mWaitingForConfirmationFlag;
|
||||
|
||||
nsString mFileName;
|
||||
nsString mContentType;
|
||||
uint32_t mFileLength;
|
||||
uint32_t mSentFileLength;
|
||||
bool mWaitingToSendPutFinal;
|
||||
|
||||
nsAutoArrayPtr<uint8_t> mBodySegment;
|
||||
nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
|
||||
|
||||
int mCurrentBlobIndex;
|
||||
nsCOMPtr<nsIDOMBlob> mBlob;
|
||||
nsTArray<SendFileBatch> mBatches;
|
||||
|
||||
/**
|
||||
* A seperate member thread is required because our read calls can block
|
||||
* execution, which is not allowed to happen on the IOThread.
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mReadFileThread;
|
||||
nsCOMPtr<nsIOutputStream> mOutputStream;
|
||||
nsCOMPtr<nsIInputStream> mInputStream;
|
||||
nsCOMPtr<nsIVolumeMountLock> mMountLock;
|
||||
nsRefPtr<DeviceStorageFile> mDsFile;
|
||||
|
||||
// If a connection has been established, mSocket will be the socket
|
||||
// communicating with the remote socket. We maintain the invariant that if
|
||||
// mSocket is non-null, mRfcommSocket and mL2capSocket must be null (and vice
|
||||
// versa).
|
||||
nsRefPtr<BluetoothSocket> mSocket;
|
||||
|
||||
// Server sockets. Once an inbound connection is established, it will hand
|
||||
// over the ownership to mSocket, and get a new server socket while Listen()
|
||||
// is called.
|
||||
nsRefPtr<BluetoothSocket> mRfcommSocket;
|
||||
nsRefPtr<BluetoothSocket> mL2capSocket;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
#endif
|
98
dom/bluetooth/bluez/BluetoothSocket.cpp
Normal file
98
dom/bluetooth/bluez/BluetoothSocket.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
/* -*- 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 "BluetoothSocket.h"
|
||||
|
||||
#include "BluetoothSocketObserver.h"
|
||||
#include "BluetoothUnixSocketConnector.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla::ipc;
|
||||
USING_BLUETOOTH_NAMESPACE
|
||||
|
||||
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
|
||||
BluetoothSocketType aType,
|
||||
bool aAuth,
|
||||
bool aEncrypt)
|
||||
: mObserver(aObserver)
|
||||
, mType(aType)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
{
|
||||
MOZ_ASSERT(aObserver);
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::Connect(const nsACString& aDeviceAddress, int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aDeviceAddress.IsEmpty());
|
||||
|
||||
nsAutoPtr<BluetoothUnixSocketConnector> c(
|
||||
new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
|
||||
|
||||
if (!ConnectSocket(c.forget(), aDeviceAddress.BeginReading())) {
|
||||
nsAutoString addr;
|
||||
GetAddress(addr);
|
||||
BT_LOGD("%s failed. Current connected device address: %s",
|
||||
__FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothSocket::Listen(int aChannel)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsAutoPtr<BluetoothUnixSocketConnector> c(
|
||||
new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
|
||||
|
||||
if (!ListenSocket(c.forget())) {
|
||||
nsAutoString addr;
|
||||
GetAddress(addr);
|
||||
BT_LOGD("%s failed. Current connected device address: %s",
|
||||
__FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->ReceiveSocketData(this, aMessage);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnConnectSuccess()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketConnectSuccess(this);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnConnectError()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketConnectError(this);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothSocket::OnDisconnect()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mObserver);
|
||||
mObserver->OnSocketDisconnect(this);
|
||||
}
|
||||
|
285
dom/bluetooth/bluez/BluetoothUnixSocketConnector.cpp
Normal file
285
dom/bluetooth/bluez/BluetoothUnixSocketConnector.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/*
|
||||
* Copyright 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* NOTE: Due to being based on the dbus compatibility layer for
|
||||
* android's bluetooth implementation, this file is licensed under the
|
||||
* apache license instead of MPL.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <bluetooth/rfcomm.h>
|
||||
#include <bluetooth/sco.h>
|
||||
#endif
|
||||
#include "BluetoothUnixSocketConnector.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla::ipc;
|
||||
USING_BLUETOOTH_NAMESPACE
|
||||
|
||||
static const int RFCOMM_SO_SNDBUF = 70 * 1024; // 70 KB send buffer
|
||||
static const int L2CAP_SO_SNDBUF = 400 * 1024; // 400 KB send buffer
|
||||
static const int L2CAP_SO_RCVBUF = 400 * 1024; // 400 KB receive buffer
|
||||
static const int L2CAP_MAX_MTU = 65000;
|
||||
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
static
|
||||
int get_bdaddr(const char *str, bdaddr_t *ba)
|
||||
{
|
||||
char *d = ((char*)ba) + 5, *endp;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
*d-- = strtol(str, &endp, 16);
|
||||
MOZ_ASSERT(!(*endp != ':' && i != 5));
|
||||
str = endp + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {
|
||||
const uint8_t *b = (const uint8_t *)ba;
|
||||
sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
|
||||
b[5], b[4], b[3], b[2], b[1], b[0]);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
BluetoothUnixSocketConnector::BluetoothUnixSocketConnector(
|
||||
BluetoothSocketType aType,
|
||||
int aChannel,
|
||||
bool aAuth,
|
||||
bool aEncrypt) : mType(aType)
|
||||
, mChannel(aChannel)
|
||||
, mAuth(aAuth)
|
||||
, mEncrypt(aEncrypt)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothUnixSocketConnector::SetUp(int aFd)
|
||||
{
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
int lm = 0;
|
||||
int sndbuf, rcvbuf;
|
||||
|
||||
/* kernel does not yet support LM for SCO */
|
||||
switch (mType) {
|
||||
case BluetoothSocketType::RFCOMM:
|
||||
lm |= mAuth ? RFCOMM_LM_AUTH : 0;
|
||||
lm |= mEncrypt ? RFCOMM_LM_ENCRYPT : 0;
|
||||
break;
|
||||
case BluetoothSocketType::L2CAP:
|
||||
case BluetoothSocketType::EL2CAP:
|
||||
lm |= mAuth ? L2CAP_LM_AUTH : 0;
|
||||
lm |= mEncrypt ? L2CAP_LM_ENCRYPT : 0;
|
||||
break;
|
||||
case BluetoothSocketType::SCO:
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unknown socket type!");
|
||||
}
|
||||
|
||||
if (lm) {
|
||||
if (mType == BluetoothSocketType::RFCOMM) {
|
||||
if (setsockopt(aFd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) {
|
||||
BT_WARNING("setsockopt(RFCOMM_LM) failed, throwing");
|
||||
return false;
|
||||
}
|
||||
} else if (mType == BluetoothSocketType::L2CAP ||
|
||||
mType == BluetoothSocketType::EL2CAP) {
|
||||
if (setsockopt(aFd, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm))) {
|
||||
BT_WARNING("setsockopt(L2CAP_LM) failed, throwing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == BluetoothSocketType::RFCOMM) {
|
||||
sndbuf = RFCOMM_SO_SNDBUF;
|
||||
if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
|
||||
BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setting L2CAP socket options */
|
||||
if (mType == BluetoothSocketType::L2CAP ||
|
||||
mType == BluetoothSocketType::EL2CAP) {
|
||||
struct l2cap_options opts;
|
||||
socklen_t optlen = sizeof(opts);
|
||||
int err;
|
||||
err = getsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen);
|
||||
if (!err) {
|
||||
/* setting MTU for [E]L2CAP */
|
||||
opts.omtu = opts.imtu = L2CAP_MAX_MTU;
|
||||
|
||||
/* Enable ERTM for [E]L2CAP */
|
||||
if (mType == BluetoothSocketType::EL2CAP) {
|
||||
opts.flush_to = 0xffff; /* infinite */
|
||||
opts.mode = L2CAP_MODE_ERTM;
|
||||
opts.fcs = 1;
|
||||
opts.txwin_size = 64;
|
||||
opts.max_tx = 10;
|
||||
}
|
||||
|
||||
err = setsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, optlen);
|
||||
}
|
||||
|
||||
/* Set larger SNDBUF & RCVBUF for EL2CAP connections */
|
||||
if (mType == BluetoothSocketType::EL2CAP) {
|
||||
sndbuf = L2CAP_SO_SNDBUF;
|
||||
if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
|
||||
BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
|
||||
return false;
|
||||
}
|
||||
|
||||
rcvbuf = L2CAP_SO_RCVBUF;
|
||||
if (setsockopt(aFd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) {
|
||||
BT_WARNING("setsockopt(SO_RCVBUF) failed, throwing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothUnixSocketConnector::SetUpListenSocket(int aFd)
|
||||
{
|
||||
// Nothing to do here.
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
BluetoothUnixSocketConnector::Create()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
int fd = -1;
|
||||
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
switch (mType) {
|
||||
case BluetoothSocketType::RFCOMM:
|
||||
fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
|
||||
break;
|
||||
case BluetoothSocketType::SCO:
|
||||
fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
|
||||
break;
|
||||
case BluetoothSocketType::L2CAP:
|
||||
fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||
break;
|
||||
case BluetoothSocketType::EL2CAP:
|
||||
fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (fd < 0) {
|
||||
BT_WARNING("Could not open bluetooth socket!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!SetUp(fd)) {
|
||||
BT_WARNING("Could not set up socket!");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothUnixSocketConnector::CreateAddr(bool aIsServer,
|
||||
socklen_t& aAddrSize,
|
||||
sockaddr_any& aAddr,
|
||||
const char* aAddress)
|
||||
{
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
// Set to BDADDR_ANY, if it's not a server, we'll reset.
|
||||
bdaddr_t bd_address_obj = {{0, 0, 0, 0, 0, 0}};
|
||||
|
||||
if (!aIsServer && aAddress && strlen(aAddress) > 0) {
|
||||
if (get_bdaddr(aAddress, &bd_address_obj)) {
|
||||
BT_WARNING("Can't get bluetooth address!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
memset(&aAddr, 0, sizeof(aAddr));
|
||||
|
||||
switch (mType) {
|
||||
case BluetoothSocketType::RFCOMM:
|
||||
struct sockaddr_rc addr_rc;
|
||||
aAddrSize = sizeof(addr_rc);
|
||||
aAddr.rc.rc_family = AF_BLUETOOTH;
|
||||
aAddr.rc.rc_channel = mChannel;
|
||||
memcpy(&aAddr.rc.rc_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
|
||||
break;
|
||||
case BluetoothSocketType::L2CAP:
|
||||
case BluetoothSocketType::EL2CAP:
|
||||
struct sockaddr_l2 addr_l2;
|
||||
aAddrSize = sizeof(addr_l2);
|
||||
aAddr.l2.l2_family = AF_BLUETOOTH;
|
||||
aAddr.l2.l2_psm = mChannel;
|
||||
memcpy(&aAddr.l2.l2_bdaddr, &bd_address_obj, sizeof(bdaddr_t));
|
||||
break;
|
||||
case BluetoothSocketType::SCO:
|
||||
struct sockaddr_sco addr_sco;
|
||||
aAddrSize = sizeof(addr_sco);
|
||||
aAddr.sco.sco_family = AF_BLUETOOTH;
|
||||
memcpy(&aAddr.sco.sco_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
|
||||
break;
|
||||
default:
|
||||
BT_WARNING("Socket type unknown!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothUnixSocketConnector::GetSocketAddr(const sockaddr_any& aAddr,
|
||||
nsAString& aAddrStr)
|
||||
{
|
||||
#ifdef MOZ_B2G_BT_BLUEZ
|
||||
char addr[18];
|
||||
switch (mType) {
|
||||
case BluetoothSocketType::RFCOMM:
|
||||
get_bdaddr_as_string((bdaddr_t*)(&aAddr.rc.rc_bdaddr), addr);
|
||||
break;
|
||||
case BluetoothSocketType::SCO:
|
||||
get_bdaddr_as_string((bdaddr_t*)(&aAddr.sco.sco_bdaddr), addr);
|
||||
break;
|
||||
case BluetoothSocketType::L2CAP:
|
||||
case BluetoothSocketType::EL2CAP:
|
||||
get_bdaddr_as_string((bdaddr_t*)(&aAddr.l2.l2_bdaddr), addr);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Socket should be either RFCOMM or SCO!");
|
||||
}
|
||||
aAddrStr.AssignASCII(addr);
|
||||
#endif
|
||||
}
|
42
dom/bluetooth/bluez/BluetoothUnixSocketConnector.h
Normal file
42
dom/bluetooth/bluez/BluetoothUnixSocketConnector.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* -*- 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_BluetoothUnixSocketConnector_h
|
||||
#define mozilla_dom_bluetooth_BluetoothUnixSocketConnector_h
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
#include <sys/socket.h>
|
||||
#include <mozilla/ipc/UnixSocket.h>
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
class BluetoothUnixSocketConnector : public mozilla::ipc::UnixSocketConnector
|
||||
{
|
||||
public:
|
||||
BluetoothUnixSocketConnector(BluetoothSocketType aType, int aChannel,
|
||||
bool aAuth, bool aEncrypt);
|
||||
virtual ~BluetoothUnixSocketConnector()
|
||||
{}
|
||||
virtual int Create() MOZ_OVERRIDE;
|
||||
virtual bool CreateAddr(bool aIsServer,
|
||||
socklen_t& aAddrSize,
|
||||
mozilla::ipc::sockaddr_any& aAddr,
|
||||
const char* aAddress) MOZ_OVERRIDE;
|
||||
virtual bool SetUp(int aFd) MOZ_OVERRIDE;
|
||||
virtual bool SetUpListenSocket(int aFd) MOZ_OVERRIDE;
|
||||
virtual void GetSocketAddr(const mozilla::ipc::sockaddr_any& aAddr,
|
||||
nsAString& aAddrStr) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
BluetoothSocketType mType;
|
||||
int mChannel;
|
||||
bool mAuth;
|
||||
bool mEncrypt;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
#endif
|
@ -2971,10 +2971,8 @@ BluetoothDBusService::SendFile(const nsAString& aDeviceAddress,
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
NS_ENSURE_TRUE_VOID(opp);
|
||||
|
||||
nsAutoString errorStr;
|
||||
if (!opp->SendFile(aDeviceAddress, aBlobParent)) {
|
||||
if (!opp || !opp->SendFile(aDeviceAddress, aBlobParent)) {
|
||||
errorStr.AssignLiteral("Calling SendFile() failed");
|
||||
}
|
||||
|
||||
@ -2992,10 +2990,8 @@ BluetoothDBusService::StopSendingFile(const nsAString& aDeviceAddress,
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
NS_ENSURE_TRUE_VOID(opp);
|
||||
|
||||
nsAutoString errorStr;
|
||||
if (!opp->StopSendingFile()) {
|
||||
if (!opp || !opp->StopSendingFile()) {
|
||||
errorStr.AssignLiteral("Calling StopSendingFile() failed");
|
||||
}
|
||||
|
||||
@ -3007,17 +3003,15 @@ BluetoothDBusService::ConfirmReceivingFile(const nsAString& aDeviceAddress,
|
||||
bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must be called from main thread!");
|
||||
|
||||
// Currently we only support one device sending one file at a time,
|
||||
// so we don't need aDeviceAddress here because the target device
|
||||
// has been determined when calling 'Connect()'. Nevertheless, keep
|
||||
// it for future use.
|
||||
BluetoothOppManager* opp = BluetoothOppManager::Get();
|
||||
NS_ENSURE_TRUE_VOID(opp);
|
||||
|
||||
nsAutoString errorStr;
|
||||
if (!opp->ConfirmReceivingFile(aConfirm)) {
|
||||
if (!opp || !opp->ConfirmReceivingFile(aConfirm)) {
|
||||
errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed");
|
||||
}
|
||||
|
||||
|
@ -12,13 +12,10 @@ if CONFIG['MOZ_B2G_BT']:
|
||||
'BluetoothDevice.cpp',
|
||||
'BluetoothHidManager.cpp',
|
||||
'BluetoothManager.cpp',
|
||||
'BluetoothOppManager.cpp',
|
||||
'BluetoothProfileController.cpp',
|
||||
'BluetoothPropertyContainer.cpp',
|
||||
'BluetoothReplyRunnable.cpp',
|
||||
'BluetoothService.cpp',
|
||||
'BluetoothSocket.cpp',
|
||||
'BluetoothUnixSocketConnector.cpp',
|
||||
'BluetoothUtils.cpp',
|
||||
'BluetoothUuid.cpp',
|
||||
'ipc/BluetoothChild.cpp',
|
||||
@ -37,6 +34,9 @@ if CONFIG['MOZ_B2G_BT']:
|
||||
SOURCES += [
|
||||
'bluez/BluetoothA2dpManager.cpp',
|
||||
'bluez/BluetoothHfpManager.cpp',
|
||||
'bluez/BluetoothOppManager.cpp',
|
||||
'bluez/BluetoothSocket.cpp',
|
||||
'bluez/BluetoothUnixSocketConnector.cpp',
|
||||
'bluez/gonk/BluetoothGonkService.cpp',
|
||||
'bluez/linux/BluetoothDBusService.cpp',
|
||||
]
|
||||
@ -50,6 +50,8 @@ if CONFIG['MOZ_B2G_BT']:
|
||||
SOURCES += [
|
||||
'bluedroid/BluetoothA2dpManager.cpp',
|
||||
'bluedroid/BluetoothHfpManager.cpp',
|
||||
'bluedroid/BluetoothOppManager.cpp',
|
||||
'bluedroid/BluetoothSocket.cpp',
|
||||
'bluedroid/gonk/BluetoothServiceBluedroid.cpp',
|
||||
]
|
||||
LOCAL_INCLUDES += [
|
||||
|
@ -27,7 +27,7 @@
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
|
||||
["dom.promise.enabled", true],
|
||||
["geo.testing.ignore_ipc_principal", true]]}, function() {
|
||||
["dom.testing.ignore_ipc_principal", true]]}, function() {
|
||||
gGenerator.next(); });
|
||||
});
|
||||
|
||||
|
@ -76,15 +76,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
|
@ -76,15 +76,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
|
@ -76,15 +76,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
|
@ -120,15 +120,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
// Enabling mozBrowser
|
||||
|
@ -76,15 +76,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
|
@ -102,7 +102,7 @@
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -76,15 +76,9 @@
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
|
||||
["dom.datastore.enabled", true],
|
||||
["dom.testing.ignore_ipc_principal", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
|
@ -24,6 +24,4 @@ qemu = true
|
||||
[test_stk_setup_idle_mode_text.js]
|
||||
[test_stk_bip_command.js]
|
||||
[test_icc_access_invalid_object.js]
|
||||
disabled = Bug 933654
|
||||
[test_icc_detected_undetected_event.js]
|
||||
disabled = Bug 933654
|
||||
|
@ -5,25 +5,18 @@ MARIONETTE_TIMEOUT = 30000;
|
||||
MARIONETTE_HEAD_JS = "icc_header.js";
|
||||
|
||||
function setRadioEnabled(enabled) {
|
||||
SpecialPowers.addPermission("settings-write", true, document);
|
||||
let connection = navigator.mozMobileConnections[0];
|
||||
ok(connection);
|
||||
|
||||
// TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
|
||||
let settings = navigator.mozSettings;
|
||||
let setLock = settings.createLock();
|
||||
let obj = {
|
||||
"ril.radio.disabled": !enabled
|
||||
let request = connection.setRadioEnabled(enabled);
|
||||
|
||||
request.onsuccess = function onsuccess() {
|
||||
log('setRadioEnabled: ' + enabled);
|
||||
};
|
||||
let setReq = setLock.set(obj);
|
||||
|
||||
setReq.addEventListener("success", function onSetSuccess() {
|
||||
log("set 'ril.radio.disabled' to " + enabled);
|
||||
});
|
||||
|
||||
setReq.addEventListener("error", function onSetError() {
|
||||
ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
|
||||
});
|
||||
|
||||
SpecialPowers.removePermission("settings-write", document);
|
||||
request.onerror = function onerror() {
|
||||
ok(false, "setRadioEnabled should be ok");
|
||||
};
|
||||
}
|
||||
|
||||
/* Test access invalid icc object */
|
||||
|
@ -5,25 +5,18 @@ MARIONETTE_TIMEOUT = 30000;
|
||||
MARIONETTE_HEAD_JS = "icc_header.js";
|
||||
|
||||
function setRadioEnabled(enabled) {
|
||||
SpecialPowers.addPermission("settings-write", true, document);
|
||||
let connection = navigator.mozMobileConnections[0];
|
||||
ok(connection);
|
||||
|
||||
// TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
|
||||
let settings = navigator.mozSettings;
|
||||
let setLock = settings.createLock();
|
||||
let obj = {
|
||||
"ril.radio.disabled": !enabled
|
||||
let request = connection.setRadioEnabled(enabled);
|
||||
|
||||
request.onsuccess = function onsuccess() {
|
||||
log('setRadioEnabled: ' + enabled);
|
||||
};
|
||||
let setReq = setLock.set(obj);
|
||||
|
||||
setReq.addEventListener("success", function onSetSuccess() {
|
||||
log("set 'ril.radio.disabled' to " + enabled);
|
||||
});
|
||||
|
||||
setReq.addEventListener("error", function onSetError() {
|
||||
ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
|
||||
});
|
||||
|
||||
SpecialPowers.removePermission("settings-write", document);
|
||||
request.onerror = function onerror() {
|
||||
ok(false, "setRadioEnabled should be ok");
|
||||
};
|
||||
}
|
||||
|
||||
/* Test iccundetected event */
|
||||
|
@ -2759,7 +2759,7 @@ ContentParent::RecvSyncMessage(const nsString& aMsg,
|
||||
InfallibleTArray<nsString>* aRetvals)
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(this, principal)) {
|
||||
return false;
|
||||
}
|
||||
@ -2783,7 +2783,7 @@ ContentParent::AnswerRpcMessage(const nsString& aMsg,
|
||||
InfallibleTArray<nsString>* aRetvals)
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(this, principal)) {
|
||||
return false;
|
||||
}
|
||||
@ -2805,7 +2805,7 @@ ContentParent::RecvAsyncMessage(const nsString& aMsg,
|
||||
const IPC::Principal& aPrincipal)
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(this, principal)) {
|
||||
return false;
|
||||
}
|
||||
@ -2861,7 +2861,7 @@ ContentParent::RecvAddGeolocationListener(const IPC::Principal& aPrincipal,
|
||||
const bool& aHighAccuracy)
|
||||
{
|
||||
#ifdef MOZ_CHILD_PERMISSIONS
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false)) {
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false)) {
|
||||
uint32_t permission = mozilla::CheckPermission(this, aPrincipal,
|
||||
"geolocation");
|
||||
if (permission != nsIPermissionManager::ALLOW_ACTION) {
|
||||
|
@ -777,7 +777,7 @@ TabParent::RecvSyncMessage(const nsString& aMessage,
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
ContentParent* parent = static_cast<ContentParent*>(Manager());
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(parent, principal)) {
|
||||
return false;
|
||||
}
|
||||
@ -796,7 +796,7 @@ TabParent::AnswerRpcMessage(const nsString& aMessage,
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
ContentParent* parent = static_cast<ContentParent*>(Manager());
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(parent, principal)) {
|
||||
return false;
|
||||
}
|
||||
@ -814,7 +814,7 @@ TabParent::RecvAsyncMessage(const nsString& aMessage,
|
||||
{
|
||||
nsIPrincipal* principal = aPrincipal;
|
||||
ContentParent* parent = static_cast<ContentParent*>(Manager());
|
||||
if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
|
||||
if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
|
||||
principal && !AssertAppPrincipal(parent, principal)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ NetworkStatsDB.prototype = {
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (!sample) {
|
||||
if (!sample && cursor.value.appId == 0) {
|
||||
sample = cursor.value;
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,13 @@
|
||||
*/
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include "AudioChannelService.h"
|
||||
#include "AudioManager.h"
|
||||
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIRadioInterfaceLayer.h"
|
||||
#include "nsISettingsService.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
@ -369,7 +371,8 @@ public:
|
||||
};
|
||||
|
||||
AudioManager::AudioManager() : mPhoneState(PHONE_STATE_CURRENT),
|
||||
mObserver(new HeadphoneSwitchObserver())
|
||||
mObserver(new HeadphoneSwitchObserver()),
|
||||
mMuteCallToRIL(false)
|
||||
{
|
||||
RegisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
|
||||
|
||||
@ -419,6 +422,12 @@ AudioManager::AudioManager() : mPhoneState(PHONE_STATE_CURRENT),
|
||||
if (NS_FAILED(obs->AddObserver(this, MOZ_SETTINGS_CHANGE_ID, false))) {
|
||||
NS_WARNING("Failed to add mozsettings-changed observer!");
|
||||
}
|
||||
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.moz.mute.call.to_ril", value, "false");
|
||||
if (!strcmp(value, "true")) {
|
||||
mMuteCallToRIL = true;
|
||||
}
|
||||
}
|
||||
|
||||
AudioManager::~AudioManager() {
|
||||
@ -443,6 +452,12 @@ AudioManager::~AudioManager() {
|
||||
NS_IMETHODIMP
|
||||
AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted)
|
||||
{
|
||||
if (mMuteCallToRIL) {
|
||||
// Simply return cached mIsMicMuted if mute call go via RIL.
|
||||
*aMicrophoneMuted = mIsMicMuted;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (AudioSystem::isMicrophoneMuted(aMicrophoneMuted)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -452,10 +467,17 @@ AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted)
|
||||
NS_IMETHODIMP
|
||||
AudioManager::SetMicrophoneMuted(bool aMicrophoneMuted)
|
||||
{
|
||||
if (AudioSystem::muteMicrophone(aMicrophoneMuted)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
if (!AudioSystem::muteMicrophone(aMicrophoneMuted)) {
|
||||
if (mMuteCallToRIL) {
|
||||
// Extra mute request to RIL for specific platform.
|
||||
nsCOMPtr<nsIRadioInterfaceLayer> ril = do_GetService("@mozilla.org/ril;1");
|
||||
NS_ENSURE_TRUE(ril, NS_ERROR_FAILURE);
|
||||
ril->SetMicrophoneMuted(aMicrophoneMuted);
|
||||
mIsMicMuted = aMicrophoneMuted;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_OK;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -66,6 +66,9 @@ protected:
|
||||
private:
|
||||
nsAutoPtr<mozilla::hal::SwitchObserver> mObserver;
|
||||
nsCOMPtr<AudioChannelAgent> mPhoneAudioAgent;
|
||||
bool mMuteCallToRIL;
|
||||
// mIsMicMuted is only used for toggling mute call to RIL.
|
||||
bool mIsMicMuted;
|
||||
|
||||
void HandleBluetoothStatusChanged(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
|
@ -728,6 +728,13 @@ RadioInterfaceLayer.prototype = {
|
||||
}
|
||||
|
||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||
},
|
||||
|
||||
setMicrophoneMuted: function setMicrophoneMuted(muted) {
|
||||
for (let clientId = 0; clientId < this.numRadioInterfaces; clientId++) {
|
||||
let radioInterface = this.radioInterfaces[clientId];
|
||||
radioInterface.workerMessenger.send("setMute", { muted: muted });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -132,7 +132,7 @@ interface nsIRadioInterface : nsISupports
|
||||
void getSmscAddress(in nsIMobileMessageCallback request);
|
||||
};
|
||||
|
||||
[scriptable, uuid(70d3a18c-4063-11e3-89de-0f9ec19fd803)]
|
||||
[scriptable, uuid(86a5c280-5641-11e3-949a-0800200c9a66)]
|
||||
interface nsIRadioInterfaceLayer : nsISupports
|
||||
{
|
||||
readonly attribute unsigned long numRadioInterfaces;
|
||||
@ -143,4 +143,6 @@ interface nsIRadioInterfaceLayer : nsISupports
|
||||
* If not available, throws exception; otherwise, a valid number.
|
||||
*/
|
||||
unsigned long getClientIdByIccId(in DOMString iccId);
|
||||
|
||||
void setMicrophoneMuted(in boolean muted);
|
||||
};
|
||||
|
@ -361,11 +361,6 @@ let RIL = {
|
||||
*/
|
||||
this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
|
||||
|
||||
/**
|
||||
* Mute or unmute the radio.
|
||||
*/
|
||||
this._muted = true;
|
||||
|
||||
/**
|
||||
* USSD session flag.
|
||||
* Only one USSD session may exist at a time, and the session is assumed
|
||||
@ -391,17 +386,6 @@ let RIL = {
|
||||
this.mergedCellBroadcastConfig = null;
|
||||
},
|
||||
|
||||
get muted() {
|
||||
return this._muted;
|
||||
},
|
||||
set muted(val) {
|
||||
val = Boolean(val);
|
||||
if (this._muted != val) {
|
||||
this.setMute(val);
|
||||
this._muted = val;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse an integer from a string, falling back to a default value
|
||||
* if the the provided value is not a string or does not contain a valid
|
||||
@ -1466,10 +1450,10 @@ let RIL = {
|
||||
* @param mute
|
||||
* Boolean to indicate whether to mute or unmute the radio.
|
||||
*/
|
||||
setMute: function setMute(mute) {
|
||||
setMute: function setMute(options) {
|
||||
Buf.newParcel(REQUEST_SET_MUTE);
|
||||
Buf.writeInt32(1);
|
||||
Buf.writeInt32(mute ? 1 : 0);
|
||||
Buf.writeInt32(options.muted ? 1 : 0);
|
||||
Buf.sendParcel();
|
||||
},
|
||||
|
||||
@ -3617,10 +3601,6 @@ let RIL = {
|
||||
if (conferenceChanged) {
|
||||
this._ensureConference();
|
||||
}
|
||||
|
||||
// Update our mute status. If there is anything in our currentCalls map then
|
||||
// we know it's a voice call and we should leave audio on.
|
||||
this.muted = (Object.getOwnPropertyNames(this.currentCalls).length === 0);
|
||||
},
|
||||
|
||||
_ensureConference: function _ensureConference() {
|
||||
@ -11575,11 +11555,17 @@ let ICCRecordHelper = {
|
||||
ICCIOHelper.loadNextRecord(options);
|
||||
} else {
|
||||
if (onsuccess) {
|
||||
RIL.iccInfoPrivate.pbrs = pbrs;
|
||||
onsuccess(pbrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (RIL.iccInfoPrivate.pbrs) {
|
||||
onsuccess(RIL.iccInfoPrivate.pbrs);
|
||||
return;
|
||||
}
|
||||
|
||||
let pbrs = [];
|
||||
ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
|
||||
callback: callback.bind(this),
|
||||
|
@ -901,7 +901,7 @@ add_test(function test_read_pbr() {
|
||||
let buf = worker.Buf;
|
||||
let io = worker.ICCIOHelper;
|
||||
|
||||
io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
|
||||
io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
|
||||
let pbr_1 = [
|
||||
0xa8, 0x05, 0xc0, 0x03, 0x4f, 0x3a, 0x01
|
||||
];
|
||||
@ -937,16 +937,24 @@ add_test(function test_read_pbr() {
|
||||
let successCb = function successCb(pbrs) {
|
||||
do_check_eq(pbrs[0].adn.fileId, 0x4f3a);
|
||||
do_check_eq(pbrs.length, 1);
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
let errorCb = function errorCb(errorMsg) {
|
||||
do_print("Reading EF_PBR failed, msg = " + errorMsg);
|
||||
do_check_true(false);
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
record.readPBR(successCb, errorCb);
|
||||
|
||||
// Check cache pbrs when 2nd call
|
||||
let ifLoadEF = false;
|
||||
io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
|
||||
ifLoadEF = true;
|
||||
}
|
||||
record.readPBR(successCb, errorCb);
|
||||
do_check_false(ifLoadEF);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -44,3 +44,4 @@ disabled = Bug 821958
|
||||
[test_emergency_label.js]
|
||||
[test_conference.js]
|
||||
[test_dsds_default_service_id.js]
|
||||
[test_call_mute.js]
|
||||
|
21
dom/telephony/test/marionette/test_call_mute.js
Normal file
21
dom/telephony/test/marionette/test_call_mute.js
Normal file
@ -0,0 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = 'head.js';
|
||||
|
||||
function test_call_mute() {
|
||||
telephony.muted = true;
|
||||
is(telephony.muted, true);
|
||||
telephony.muted = false;
|
||||
is(telephony.muted, false);
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
finish();
|
||||
}
|
||||
|
||||
startTest(function() {
|
||||
test_call_mute();
|
||||
});
|
@ -80,7 +80,7 @@ function run_test()
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
prefs.setCharPref("geo.wifi.uri", "http://localhost:" +
|
||||
httpserver.identity.primaryPort + "/geo");
|
||||
prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("dom.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("geo.wifi.scan", false);
|
||||
|
||||
var obs = Cc["@mozilla.org/observer-service;1"].getService();
|
||||
|
@ -78,7 +78,7 @@ function run_test()
|
||||
providerContract, false, true);
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("dom.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("geo.wifi.scan", false);
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ function run_test()
|
||||
providerContract, false, true);
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("dom.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("geo.wifi.scan", false);
|
||||
|
||||
run_test_in_child("test_geolocation_reset_accuracy.js", check_results);
|
||||
|
@ -59,7 +59,7 @@ function run_test()
|
||||
prefs.setBoolPref("geo.wifi.scan", false);
|
||||
prefs.setCharPref("geo.wifi.uri", "http://localhost:" +
|
||||
httpserver.identity.primaryPort + "/geo");
|
||||
prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("dom.testing.ignore_ipc_principal", true);
|
||||
}
|
||||
|
||||
geolocation = Cc["@mozilla.org/geolocation;1"].getService(Ci.nsISupports);
|
||||
|
@ -14,6 +14,6 @@ function run_test() {
|
||||
httpserver.start(-1);
|
||||
prefs.setCharPref("geo.wifi.uri", "http://localhost:" +
|
||||
httpserver.identity.primaryPort + "/geo");
|
||||
prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
|
||||
prefs.setBoolPref("dom.testing.ignore_ipc_principal", true);
|
||||
run_test_in_child("./test_geolocation_timeout.js");
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ SourceSurfaceSkia::InitFromData(unsigned char* aData,
|
||||
if (aFormat == FORMAT_B8G8R8X8) {
|
||||
mBitmap.lockPixels();
|
||||
// We have to manually set the A channel to be 255 as Skia doesn't understand BGRX
|
||||
ConvertBGRXToBGRA(reinterpret_cast<unsigned char*>(mBitmap.getPixels()), aSize, aStride);
|
||||
ConvertBGRXToBGRA(reinterpret_cast<unsigned char*>(mBitmap.getPixels()), aSize, mBitmap.rowBytes());
|
||||
mBitmap.unlockPixels();
|
||||
mBitmap.notifyPixelsChanged();
|
||||
mBitmap.setIsOpaque(true);
|
||||
@ -83,7 +83,7 @@ SourceSurfaceSkia::InitFromData(unsigned char* aData,
|
||||
|
||||
mSize = aSize;
|
||||
mFormat = aFormat;
|
||||
mStride = aStride;
|
||||
mStride = mBitmap.rowBytes();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include "TextureGarbageBin.h"
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
#include "OGLShaderProgram.h" // for ShaderProgramType
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
@ -41,6 +43,7 @@
|
||||
#endif
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::layers;
|
||||
|
||||
namespace mozilla {
|
||||
namespace gl {
|
||||
@ -273,6 +276,11 @@ GLContext::GLContext(const SurfaceCaps& caps,
|
||||
mWorkAroundDriverBugs(true)
|
||||
{
|
||||
mOwningThread = NS_GetCurrentThread();
|
||||
|
||||
mReadTextureImagePrograms[0] = 0;
|
||||
mReadTextureImagePrograms[1] = 0;
|
||||
mReadTextureImagePrograms[2] = 0;
|
||||
mReadTextureImagePrograms[3] = 0;
|
||||
}
|
||||
|
||||
GLContext::~GLContext() {
|
||||
@ -1855,6 +1863,11 @@ GLContext::MarkDestroyed()
|
||||
mBlitHelper = nullptr;
|
||||
mBlitTextureImageHelper = nullptr;
|
||||
|
||||
fDeleteProgram(mReadTextureImagePrograms[0]);
|
||||
fDeleteProgram(mReadTextureImagePrograms[1]);
|
||||
fDeleteProgram(mReadTextureImagePrograms[2]);
|
||||
fDeleteProgram(mReadTextureImagePrograms[3]);
|
||||
|
||||
mTexGarbageBin->GLContextTeardown();
|
||||
} else {
|
||||
NS_WARNING("MakeCurrent() failed during MarkDestroyed! Skipping GL object teardown.");
|
||||
@ -1938,144 +1951,340 @@ GLContext::GetTexImage(GLuint aTexture, bool aYInvert, SurfaceFormat aFormat)
|
||||
return surf.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<gfxImageSurface>
|
||||
GLContext::ReadTextureImage(GLuint aTexture,
|
||||
const gfxIntSize& aSize,
|
||||
GLenum aTextureFormat,
|
||||
bool aYInvert)
|
||||
static float
|
||||
gReadTextureImageVerts[4*4] = {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||
1.0f, 1.0f, 0.0f, 1.0f
|
||||
};
|
||||
|
||||
static float*
|
||||
ReadTextureVertexArray()
|
||||
{
|
||||
MakeCurrent();
|
||||
return gReadTextureImageVerts;
|
||||
}
|
||||
|
||||
nsRefPtr<gfxImageSurface> isurf;
|
||||
static float
|
||||
gReadTextureImageTexcoords[2*4] = {
|
||||
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
GLint oldrb, oldfb, oldprog, oldPackAlignment;
|
||||
GLint success;
|
||||
static float*
|
||||
ReadTextureTexCoordArray(float aWidth, float aHeight, bool aFlip)
|
||||
{
|
||||
const float u0 = 0.0f;
|
||||
const float u1 = aWidth;
|
||||
const float v0 = (aFlip) ? aHeight : 0.0f;
|
||||
const float v1 = (aFlip) ? 0.0f : aHeight;
|
||||
|
||||
GLuint rb = 0, fb = 0;
|
||||
GLuint vs = 0, fs = 0, prog = 0;
|
||||
float* uvs = gReadTextureImageTexcoords;
|
||||
uvs[0] = u0;
|
||||
uvs[1] = v0;
|
||||
uvs[2] = u1;
|
||||
uvs[3] = v0;
|
||||
uvs[4] = u0;
|
||||
uvs[5] = v1;
|
||||
uvs[6] = u1;
|
||||
uvs[7] = v1;
|
||||
|
||||
const char *vShader =
|
||||
"attribute vec4 aVertex;\n"
|
||||
"attribute vec2 aTexCoord;\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"void main() { gl_Position = aVertex; vTexCoord = aTexCoord; }";
|
||||
const char *fShader =
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"uniform sampler2D uTexture;\n"
|
||||
"void main() { gl_FragColor = texture2D(uTexture, vTexCoord); }";
|
||||
return uvs;
|
||||
}
|
||||
|
||||
float verts[4*4] = {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||
1.0f, 1.0f, 0.0f, 1.0f
|
||||
};
|
||||
static const char*
|
||||
gReadTextureImageVS =
|
||||
"attribute vec4 aVertex;\n"
|
||||
"attribute vec2 aTexCoord;\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"void main() { gl_Position = aVertex; vTexCoord = aTexCoord; }";
|
||||
|
||||
float texcoords[2*4] = {
|
||||
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
|
||||
};
|
||||
static const char*
|
||||
gReadTextureImageFS[] = {
|
||||
/* TEXTURE_2D */
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"uniform sampler2D uTexture;\n"
|
||||
"void main() { gl_FragColor = texture2D(uTexture, vTexCoord); }"
|
||||
,
|
||||
/* TEXTURE_2D with R/B swizzling */
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"uniform sampler2D uTexture;\n"
|
||||
"void main() { gl_FragColor = texture2D(uTexture, vTexCoord).bgra; }"
|
||||
,
|
||||
/* TEXTURE_EXTERNAL */
|
||||
"#extension GL_OES_EGL_image_external : require\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"uniform samplerExternalOES uTexture;\n"
|
||||
"void main() { gl_FragColor = texture2D(uTexture, vTexCoord); }"
|
||||
,
|
||||
/* TEXTURE_RECTANGLE */
|
||||
"#extension GL_ARB_texture_rectangle\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"varying vec2 vTexCoord;\n"
|
||||
"uniform sampler2DRect uTexture;\n"
|
||||
"void main() { gl_FragColor = texture2DRect(uTexture, vTexCoord).bgra; }"
|
||||
};
|
||||
|
||||
fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &oldrb);
|
||||
fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &oldprog);
|
||||
GLuint
|
||||
GLContext::TextureImageProgramFor(GLenum aTextureTarget, int aShader) {
|
||||
int variant = 0;
|
||||
if (aTextureTarget == LOCAL_GL_TEXTURE_2D &&
|
||||
(aShader == layers::BGRALayerProgramType ||
|
||||
aShader == layers::BGRXLayerProgramType))
|
||||
{ // Need to swizzle R/B.
|
||||
variant = 1;
|
||||
} else if (aTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL) {
|
||||
variant = 2;
|
||||
} else if (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) {
|
||||
variant = 3;
|
||||
}
|
||||
|
||||
/* This might be overkill, but assure that we don't access out-of-bounds */
|
||||
MOZ_ASSERT((size_t) variant < ArrayLength(mReadTextureImagePrograms));
|
||||
if (!mReadTextureImagePrograms[variant]) {
|
||||
GLuint vs = fCreateShader(LOCAL_GL_VERTEX_SHADER);
|
||||
fShaderSource(vs, 1, (const GLchar**) &gReadTextureImageVS, NULL);
|
||||
fCompileShader(vs);
|
||||
|
||||
GLuint fs = fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
|
||||
fShaderSource(fs, 1, (const GLchar**) &gReadTextureImageFS[variant], NULL);
|
||||
fCompileShader(fs);
|
||||
|
||||
GLuint program = fCreateProgram();
|
||||
fAttachShader(program, vs);
|
||||
fAttachShader(program, fs);
|
||||
fBindAttribLocation(program, 0, "aVertex");
|
||||
fBindAttribLocation(program, 1, "aTexCoord");
|
||||
fLinkProgram(program);
|
||||
|
||||
GLint success;
|
||||
fGetProgramiv(program, LOCAL_GL_LINK_STATUS, &success);
|
||||
|
||||
if (!success) {
|
||||
fDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
|
||||
fDeleteShader(vs);
|
||||
fDeleteShader(fs);
|
||||
|
||||
mReadTextureImagePrograms[variant] = program;
|
||||
}
|
||||
|
||||
return mReadTextureImagePrograms[variant];
|
||||
}
|
||||
|
||||
static bool
|
||||
DidGLErrorOccur(GLContext* aGL, const char* str)
|
||||
{
|
||||
GLenum error = aGL->fGetError();
|
||||
if (error != LOCAL_GL_NO_ERROR) {
|
||||
printf_stderr("GL ERROR: %s (0x%04x) %s\n",
|
||||
aGL->GLErrorToString(error), error, str);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
GLContext::ReadBackPixelsIntoSurface(gfxImageSurface* aSurface, const gfxIntSize& aSize) {
|
||||
GLint oldPackAlignment;
|
||||
fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, &oldPackAlignment);
|
||||
|
||||
PushViewportRect(nsIntRect(0, 0, aSize.width, aSize.height));
|
||||
|
||||
fGenRenderbuffers(1, &rb);
|
||||
fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb);
|
||||
fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_RGBA,
|
||||
aSize.width, aSize.height);
|
||||
|
||||
fGenFramebuffers(1, &fb);
|
||||
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb);
|
||||
fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
|
||||
LOCAL_GL_RENDERBUFFER, rb);
|
||||
|
||||
if (fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) !=
|
||||
LOCAL_GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
vs = fCreateShader(LOCAL_GL_VERTEX_SHADER);
|
||||
fs = fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
|
||||
fShaderSource(vs, 1, (const GLchar**) &vShader, nullptr);
|
||||
fShaderSource(fs, 1, (const GLchar**) &fShader, nullptr);
|
||||
fCompileShader(vs);
|
||||
fCompileShader(fs);
|
||||
prog = fCreateProgram();
|
||||
fAttachShader(prog, vs);
|
||||
fAttachShader(prog, fs);
|
||||
fBindAttribLocation(prog, 0, "aVertex");
|
||||
fBindAttribLocation(prog, 1, "aTexCoord");
|
||||
fLinkProgram(prog);
|
||||
|
||||
fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fUseProgram(prog);
|
||||
|
||||
fEnableVertexAttribArray(0);
|
||||
fEnableVertexAttribArray(1);
|
||||
|
||||
fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, verts);
|
||||
fVertexAttribPointer(1, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, texcoords);
|
||||
|
||||
fActiveTexture(LOCAL_GL_TEXTURE0);
|
||||
fBindTexture(LOCAL_GL_TEXTURE_2D, aTexture);
|
||||
|
||||
fUniform1i(fGetUniformLocation(prog, "uTexture"), 0);
|
||||
|
||||
fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
fDisableVertexAttribArray(1);
|
||||
fDisableVertexAttribArray(0);
|
||||
|
||||
isurf = new gfxImageSurface(aSize, gfxImageFormatARGB32);
|
||||
if (!isurf || isurf->CairoStatus()) {
|
||||
isurf = nullptr;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (oldPackAlignment != 4)
|
||||
fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
|
||||
|
||||
fReadPixels(0, 0, aSize.width, aSize.height,
|
||||
LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
|
||||
isurf->Data());
|
||||
aSurface->Data());
|
||||
|
||||
SwapRAndBComponents(isurf);
|
||||
bool result = DidGLErrorOccur(this, "when reading pixels into surface");
|
||||
|
||||
if (oldPackAlignment != 4)
|
||||
fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, oldPackAlignment);
|
||||
|
||||
if (aYInvert) {
|
||||
isurf = YInvertImageSurface(isurf);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CLEANUP_IF_GLERROR_OCCURRED(x) \
|
||||
if (DidGLErrorOccur(this, (x))) { \
|
||||
isurf = nullptr; \
|
||||
break; \
|
||||
}
|
||||
|
||||
cleanup:
|
||||
// note that deleting 0 has no effect in any of these calls
|
||||
fDeleteRenderbuffers(1, &rb);
|
||||
fDeleteFramebuffers(1, &fb);
|
||||
fDeleteShader(vs);
|
||||
fDeleteShader(fs);
|
||||
fDeleteProgram(prog);
|
||||
already_AddRefed<gfxImageSurface>
|
||||
GLContext::ReadTextureImage(GLuint aTextureId,
|
||||
GLenum aTextureTarget,
|
||||
const gfxIntSize& aSize,
|
||||
/* ShaderProgramType */ int aShaderProgram,
|
||||
bool aYInvert)
|
||||
{
|
||||
// Check aShaderProgram is in bounds for a layers::ShaderProgramType
|
||||
MOZ_ASSERT(0 <= aShaderProgram && aShaderProgram < NumProgramTypes);
|
||||
|
||||
if (aTextureTarget != LOCAL_GL_TEXTURE_2D &&
|
||||
aTextureTarget != LOCAL_GL_TEXTURE_EXTERNAL &&
|
||||
aTextureTarget != LOCAL_GL_TEXTURE_RECTANGLE_ARB)
|
||||
{
|
||||
printf_stderr("ReadTextureImage target is not TEXTURE_2D || "
|
||||
"TEXTURE_EXTERNAL || TEXTURE_RECTANGLE\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MakeCurrent();
|
||||
|
||||
/* Allocate resulting image surface */
|
||||
nsRefPtr<gfxImageSurface> isurf;
|
||||
isurf = new gfxImageSurface(aSize, gfxImageFormatARGB32);
|
||||
if (!isurf || isurf->CairoStatus()) {
|
||||
isurf = nullptr;
|
||||
return isurf.forget();
|
||||
}
|
||||
|
||||
realGLboolean oldBlend, oldScissor;
|
||||
GLint oldrb, oldfb, oldprog, oldTexUnit, oldTex;
|
||||
GLuint rb, fb;
|
||||
|
||||
do {
|
||||
/* Save current GL state */
|
||||
oldBlend = fIsEnabled(LOCAL_GL_BLEND);
|
||||
oldScissor = fIsEnabled(LOCAL_GL_SCISSOR_TEST);
|
||||
|
||||
fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &oldrb);
|
||||
fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &oldprog);
|
||||
fGetIntegerv(LOCAL_GL_ACTIVE_TEXTURE, &oldTexUnit);
|
||||
fActiveTexture(LOCAL_GL_TEXTURE0);
|
||||
switch (aTextureTarget) {
|
||||
case LOCAL_GL_TEXTURE_2D:
|
||||
fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex);
|
||||
break;
|
||||
case LOCAL_GL_TEXTURE_EXTERNAL:
|
||||
fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &oldTex);
|
||||
break;
|
||||
case LOCAL_GL_TEXTURE_RECTANGLE:
|
||||
fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &oldTex);
|
||||
break;
|
||||
default: /* Already checked above */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set required GL state */
|
||||
fDisable(LOCAL_GL_BLEND);
|
||||
fDisable(LOCAL_GL_SCISSOR_TEST);
|
||||
|
||||
PushViewportRect(nsIntRect(0, 0, aSize.width, aSize.height));
|
||||
|
||||
/* Setup renderbuffer */
|
||||
fGenRenderbuffers(1, &rb);
|
||||
fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb);
|
||||
|
||||
GLenum rbInternalFormat =
|
||||
IsGLES2()
|
||||
? (IsExtensionSupported(OES_rgb8_rgba8) ? LOCAL_GL_RGBA8 : LOCAL_GL_RGBA4)
|
||||
: LOCAL_GL_RGBA;
|
||||
fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, rbInternalFormat, aSize.width, aSize.height);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when binding and creating renderbuffer");
|
||||
|
||||
/* Setup framebuffer */
|
||||
fGenFramebuffers(1, &fb);
|
||||
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb);
|
||||
fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
|
||||
LOCAL_GL_RENDERBUFFER, rb);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when binding and creating framebuffer");
|
||||
|
||||
if (fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
|
||||
printf_stderr("framebuffer is incomplete\n");
|
||||
break; //goto cleanup;
|
||||
}
|
||||
|
||||
/* Setup vertex and fragment shader */
|
||||
layers::ShaderProgramType shaderProgram = (ShaderProgramType) aShaderProgram;
|
||||
GLuint program = TextureImageProgramFor(aTextureTarget, shaderProgram);
|
||||
if (!program) {
|
||||
printf_stderr("failed to compile program for texture target %u and"
|
||||
" shader program type %d\n",
|
||||
aTextureTarget, aShaderProgram);
|
||||
break; // goto cleanup;
|
||||
}
|
||||
|
||||
fUseProgram(program);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when using program");
|
||||
fUniform1i(fGetUniformLocation(program, "uTexture"), 0);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when setting uniform location");
|
||||
|
||||
/* Setup quad geometry */
|
||||
fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
|
||||
fEnableVertexAttribArray(0);
|
||||
fEnableVertexAttribArray(1);
|
||||
|
||||
float w = (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) ? (float) aSize.width : 1.0f;
|
||||
float h = (aTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE) ? (float) aSize.height : 1.0f;
|
||||
fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, ReadTextureVertexArray());
|
||||
fVertexAttribPointer(1, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, ReadTextureTexCoordArray(w, h, aYInvert));
|
||||
|
||||
/* Bind the texture */
|
||||
if (aTextureId) {
|
||||
fBindTexture(aTextureTarget, aTextureId);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when binding texture");
|
||||
}
|
||||
|
||||
/* Draw quad */
|
||||
fClearColor(1.0f, 0.0f, 1.0f, 1.0f);
|
||||
fClear(LOCAL_GL_COLOR_BUFFER_BIT);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when clearing color buffer");
|
||||
|
||||
fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when drawing texture");
|
||||
|
||||
fDisableVertexAttribArray(1);
|
||||
fDisableVertexAttribArray(0);
|
||||
|
||||
/* Read-back draw results */
|
||||
ReadBackPixelsIntoSurface(isurf, aSize);
|
||||
CLEANUP_IF_GLERROR_OCCURRED("when reading pixels into surface");
|
||||
} while (false);
|
||||
|
||||
/* Restore GL state */
|
||||
//cleanup:
|
||||
fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, oldrb);
|
||||
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, oldfb);
|
||||
fUseProgram(oldprog);
|
||||
|
||||
// note that deleting 0 has no effect in any of these calls
|
||||
fDeleteRenderbuffers(1, &rb);
|
||||
fDeleteFramebuffers(1, &fb);
|
||||
|
||||
if (oldBlend)
|
||||
fEnable(LOCAL_GL_BLEND);
|
||||
|
||||
if (oldScissor)
|
||||
fEnable(LOCAL_GL_SCISSOR_TEST);
|
||||
|
||||
if (aTextureId)
|
||||
fBindTexture(aTextureTarget, oldTex);
|
||||
|
||||
if (oldTexUnit != LOCAL_GL_TEXTURE0)
|
||||
fActiveTexture(oldTexUnit);
|
||||
|
||||
PopViewportRect();
|
||||
|
||||
return isurf.forget();
|
||||
}
|
||||
|
||||
#undef CLEANUP_IF_GLERROR_OCCURRED
|
||||
|
||||
static bool
|
||||
GetActualReadFormats(GLContext* gl, GLenum destFormat, GLenum destType,
|
||||
GLenum& readFormat, GLenum& readType)
|
||||
@ -2810,8 +3019,7 @@ GLContext::TexSubImage2DWithoutUnpackSubimage(GLenum target, GLint level,
|
||||
fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#ifdef MOZ_ENABLE_GL_TRACKING
|
||||
void
|
||||
GLContext::CreatedProgram(GLContext *aOrigin, GLuint aName)
|
||||
{
|
||||
|
@ -42,6 +42,10 @@
|
||||
#include "mozilla/GenericRefCounted.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#define MOZ_ENABLE_GL_TRACKING 1
|
||||
#endif
|
||||
|
||||
class nsIntRegion;
|
||||
class nsIRunnable;
|
||||
class nsIThread;
|
||||
@ -583,7 +587,7 @@ private:
|
||||
#undef BEFORE_GL_CALL
|
||||
#undef AFTER_GL_CALL
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef MOZ_ENABLE_GL_TRACKING
|
||||
|
||||
#ifndef MOZ_FUNCTION_NAME
|
||||
# ifdef __GNUC__
|
||||
@ -2380,14 +2384,14 @@ public:
|
||||
|
||||
virtual bool MakeCurrentImpl(bool aForce = false) = 0;
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef MOZ_ENABLE_GL_TRACKING
|
||||
static void StaticInit() {
|
||||
PR_NewThreadPrivateIndex(&sCurrentGLContextTLS, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool MakeCurrent(bool aForce = false) {
|
||||
#ifdef DEBUG
|
||||
#ifdef MOZ_ENABLE_GL_TRACKING
|
||||
PR_SetThreadPrivate(sCurrentGLContextTLS, this);
|
||||
|
||||
// XXX this assertion is disabled because it's triggering on Mac;
|
||||
@ -2730,6 +2734,14 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Helpers for ReadTextureImage
|
||||
*/
|
||||
GLuint TextureImageProgramFor(GLenum aTextureTarget, int aShader);
|
||||
bool ReadBackPixelsIntoSurface(gfxImageSurface* aSurface, const gfxIntSize& aSize);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Read the image data contained in aTexture, and return it as an ImageSurface.
|
||||
* If GL_RGBA is given as the format, a gfxImageFormatARGB32 surface is returned.
|
||||
@ -2740,13 +2752,20 @@ public:
|
||||
* THIS IS EXPENSIVE. It is ridiculously expensive. Only do this
|
||||
* if you absolutely positively must, and never in any performance
|
||||
* critical path.
|
||||
*
|
||||
* NOTE: aShaderProgram is really mozilla::layers::ShaderProgramType. It is
|
||||
* passed as int to eliminate including LayerManagerOGLProgram.h in this
|
||||
* hub header.
|
||||
*/
|
||||
already_AddRefed<gfxImageSurface> ReadTextureImage(GLuint aTexture,
|
||||
already_AddRefed<gfxImageSurface> ReadTextureImage(GLuint aTextureId,
|
||||
GLenum aTextureTarget,
|
||||
const gfxIntSize& aSize,
|
||||
GLenum aTextureFormat,
|
||||
/* ShaderProgramType */ int aShaderProgram,
|
||||
bool aYInvert = false);
|
||||
|
||||
already_AddRefed<gfxImageSurface> GetTexImage(GLuint aTexture, bool aYInvert, SurfaceFormat aFormat);
|
||||
already_AddRefed<gfxImageSurface> GetTexImage(GLuint aTexture,
|
||||
bool aYInvert,
|
||||
SurfaceFormat aFormat);
|
||||
|
||||
/**
|
||||
* Call ReadPixels into an existing gfxImageSurface.
|
||||
@ -3107,6 +3126,8 @@ public:
|
||||
protected:
|
||||
nsDataHashtable<nsPtrHashKey<void>, void*> mUserData;
|
||||
|
||||
GLuint mReadTextureImagePrograms[4];
|
||||
|
||||
bool InitWithPrefix(const char *prefix, bool trygl);
|
||||
|
||||
void InitExtensions();
|
||||
@ -3242,7 +3263,7 @@ public:
|
||||
|
||||
#undef ASSERT_SYMBOL_PRESENT
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef MOZ_ENABLE_GL_TRACKING
|
||||
void CreatedProgram(GLContext *aOrigin, GLuint aName);
|
||||
void CreatedShader(GLContext *aOrigin, GLuint aName);
|
||||
void CreatedBuffers(GLContext *aOrigin, GLsizei aCount, GLuint *aNames);
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
// OES_EGL_image_external
|
||||
#define LOCAL_GL_TEXTURE_EXTERNAL 0x8D65
|
||||
#define LOCAL_GL_TEXTURE_BINDING_EXTERNAL 0x8D67
|
||||
|
||||
// EGL_KHR_fence_sync
|
||||
#define LOCAL_EGL_SYNC_FENCE 0x30F9
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user