Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-12-02 16:58:15 -05:00
commit 4829857b56
126 changed files with 7580 additions and 979 deletions

View File

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

View File

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

View File

@ -1,4 +1,4 @@
{
"revision": "cf23b263b4bbf1024210350f24c7e6eb5872a4be",
"revision": "c2506884aba22b968d3ab1ad6c28f0782b22a17f",
"repo_path": "/integration/gaia-central"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ VideoFrame::TakeFrom(VideoFrame* aFrame)
{
mImage = aFrame->mImage.forget();
mIntrinsicSize = aFrame->mIntrinsicSize;
mForceBlack = aFrame->GetForceBlack();
}
VideoChunk::VideoChunk()

View File

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

View File

@ -6,6 +6,7 @@
#ifndef EncodedFrameContainer_H_
#define EncodedFrameContainer_H_
#include "nsAutoPtr.h"
#include "nsTArray.h"
namespace mozilla {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View 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

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

View 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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View 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

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,3 +44,4 @@ disabled = Bug 821958
[test_emergency_label.js]
[test_conference.js]
[test_dsds_default_service_id.js]
[test_call_mute.js]

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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