From bae45fa1c66b886bce1b1ecde48c5b93bf9b34f5 Mon Sep 17 00:00:00 2001 From: Marina Samuel Date: Mon, 2 Dec 2013 11:17:07 -0500 Subject: [PATCH 01/67] Bug 942915: Part 1: Add 'Metro Mode' as a default button in Windows 8 Australis. --- browser/components/customizableui/src/CustomizableUI.jsm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/browser/components/customizableui/src/CustomizableUI.jsm b/browser/components/customizableui/src/CustomizableUI.jsm index 578e0961490c..916e4348e611 100644 --- a/browser/components/customizableui/src/CustomizableUI.jsm +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -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 From 9373ae764c836ea04c25760e81876a97613c3e56 Mon Sep 17 00:00:00 2001 From: Marina Samuel Date: Mon, 2 Dec 2013 11:17:14 -0500 Subject: [PATCH 02/67] Bug 942915: Part 2: Update tests for Metro button Australis. --- ...er_876944_customize_mode_create_destroy.js | 4 +- ...owser_880382_drag_wide_widgets_in_panel.js | 24 ++++++- .../browser_890140_orphaned_placeholders.js | 67 +++++++++++++++---- .../components/customizableui/test/head.js | 15 +++++ 4 files changed, 92 insertions(+), 18 deletions(-) diff --git a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js index 1c25cf345b2e..d51266b4a74a 100644 --- a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js +++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js @@ -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"); diff --git a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js index 9c1af8191522..027a830eb837 100644 --- a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js +++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js @@ -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); diff --git a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js index 601c59ecd5c2..4a2fd42b96f0 100644 --- a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js +++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js @@ -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."); }, diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js index 84105579cf32..5e1b7ba8e8eb 100644 --- a/browser/components/customizableui/test/head.js +++ b/browser/components/customizableui/test/head.js @@ -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, From 0e3783e0d3cef617bd19bee2e61f465abe35cdd5 Mon Sep 17 00:00:00 2001 From: teodora vermesan Date: Thu, 21 Nov 2013 12:17:48 +0200 Subject: [PATCH 03/67] Bug 846340 - Robocop: Add test for 'Clear Site settings', 'Clear Saved passwords' and 'Clear history' options. r=gbrown --- mobile/android/base/tests/StringHelper.java | 7 ++ .../base/tests/testClearPrivateData.java | 81 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/mobile/android/base/tests/StringHelper.java b/mobile/android/base/tests/StringHelper.java index 1d51e07fc697..511e000342c8 100644 --- a/mobile/android/base/tests/StringHelper.java +++ b/mobile/android/base/tests/StringHelper.java @@ -48,6 +48,13 @@ class StringHelper { "Add to Home Screen" }; + public static final String[] CONTEXT_MENU_ITEMS_IN_URL_BAR = new String[] { + "Share", + "Copy Address", + "Edit Site Settings", + "Add to Home Screen" + }; + public static final String TITLE_PLACE_HOLDER = "Enter Search or Address"; // Robocop page urls diff --git a/mobile/android/base/tests/testClearPrivateData.java b/mobile/android/base/tests/testClearPrivateData.java index 573db9c05e52..edbb22f3cae3 100644 --- a/mobile/android/base/tests/testClearPrivateData.java +++ b/mobile/android/base/tests/testClearPrivateData.java @@ -1,8 +1,17 @@ package org.mozilla.gecko.tests; - +import android.view.View; import org.mozilla.gecko.*; import java.util.ArrayList; +/** + * This patch tests the clear private data options: + * - clear history option by: adding and checking that clear private + * data option removes the history items but not the users bookmarks + * - clear site settings and clear saved password by: checking + * each option present in the doorhanger and clearing the settings from + * the URL bar context menu and settings menu + */ + public class testClearPrivateData extends PixelTest { private final int TEST_WAIT_MS = 10000; @@ -14,21 +23,25 @@ public class testClearPrivateData extends PixelTest { public void testClearPrivateData() { blockForGeckoReady(); clearHistory(); + clearSiteSettings(); + clearPassword(); } private void clearHistory() { + // Loading a page and adding a second one as bookmark to have user made bookmarks and history String blank1 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL); String blank2 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL); - - loadAndPaint(blank1); - waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); - + String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE; + inputAndLoadUrl(blank1); + verifyPageTitle(title); mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, blank2); // Checking that the history list is not empty verifyHistoryCount(1); - clearPrivateData(); + + //clear and check for device + checkDevice(title); // Checking that history list is empty verifyHistoryCount(0); @@ -45,4 +58,60 @@ public class testClearPrivateData extends PixelTest { }, TEST_WAIT_MS); mAsserter.ok(match, "Checking that the number of history items is correct", String.valueOf(expectedCount) + " history items present in the database"); } + + public void clearSiteSettings() { + String shareStrings[] = {"Share your location with", "Share", "Don't share", "There are no settings to clear"}; + String titleGeolocation = StringHelper.ROBOCOP_GEOLOCATION_TITLE; + String url = getAbsoluteUrl(StringHelper.ROBOCOP_GEOLOCATION_URL); + loadCheckDismiss(shareStrings[1], url, shareStrings[0]); + checkOption(shareStrings[1], "Clear"); + checkOption(shareStrings[3], "Cancel"); + loadCheckDismiss(shareStrings[2], url, shareStrings[0]); + checkOption(shareStrings[2], "Cancel"); + checkDevice(titleGeolocation); + } + + public void clearPassword(){ + String passwordStrings[] = {"Save password", "Save", "Don't save"}; + String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE; + String loginUrl = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_URL); + loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]); + checkOption(passwordStrings[1], "Clear"); + loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]); + checkDevice(title); + } + + // clear private data and verify the device type because for phone there is an extra back action to exit the settings menu + public void checkDevice(String title) { + clearPrivateData(); + if (mDevice.type.equals("phone")) { + mActions.sendSpecialKey(Actions.SpecialKey.BACK); + mAsserter.ok(waitForText(StringHelper.PRIVACY_SECTION_LABEL), "waiting to perform one back", "one back"); + mActions.sendSpecialKey(Actions.SpecialKey.BACK); + verifyPageTitle(title); + } + else { + mActions.sendSpecialKey(Actions.SpecialKey.BACK); + verifyPageTitle(title); + } + } + + // Load a URL, verify that the doorhanger appears and dismiss it + public void loadCheckDismiss(String option, String url, String message) { + inputAndLoadUrl(url); + waitForText(message); + mAsserter.is(mSolo.searchText(message), true, "Doorhanger:" + message + " has been displayed"); + mSolo.clickOnButton(option); + mAsserter.is(mSolo.searchText(message), false, "Doorhanger:" + message + " has been hidden"); + } + + //Verify if there are settings to be clear if so clear them from the URL bar context menu + public void checkOption(String option, String button) { + final View toolbarView = mSolo.getView("browser_toolbar"); + mSolo.clickLongOnView(toolbarView); + mAsserter.ok(waitForText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]), "Waiting for the pop-up to open", "Pop up was openend"); + mSolo.clickOnText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]); + mAsserter.ok(waitForText(option), "Verify that the option: " + option + " is in the list", "The option is in the list. There are settings to clear"); + mSolo.clickOnButton(button); + } } From d07aab22f19661ab1167abe09144220f90e3168d Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Fri, 8 Nov 2013 09:16:04 -0500 Subject: [PATCH 04/67] Bug 854169 - Move OS.File's normalizeToPointer() to SharedAll. r=froydnj --- .../modules/osfile_shared_allthreads.jsm | 46 +++++++++++++++++ .../osfile/modules/osfile_shared_front.jsm | 49 +------------------ 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm index 907bdf03fd5b..b864b2f612ed 100644 --- a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm +++ b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm @@ -43,6 +43,7 @@ let EXPORTED_SYMBOLS = [ "declareFFI", "declareLazy", "declareLazyFFI", + "normalizeToPointer", "projectValue", "isTypedArray", "defineLazyGetter", @@ -1047,6 +1048,51 @@ let offsetBy = }; exports.offsetBy = offsetBy; +/** + * Utility function used to normalize a Typed Array or C + * pointer into a uint8_t C pointer. + * + * Future versions might extend this to other data structures. + * + * @param {Typed array | C pointer} candidate The buffer. If + * a C pointer, it must be non-null. + * @param {number} bytes The number of bytes that |candidate| should contain. + * Used for sanity checking if the size of |candidate| can be determined. + * + * @return {ptr:{C pointer}, bytes:number} A C pointer of type uint8_t, + * corresponding to the start of |candidate|. + */ +function normalizeToPointer(candidate, bytes) { + if (!candidate) { + throw new TypeError("Expecting a Typed Array or a C pointer"); + } + let ptr; + if ("isNull" in candidate) { + if (candidate.isNull()) { + throw new TypeError("Expecting a non-null pointer"); + } + ptr = Type.uint8_t.out_ptr.cast(candidate); + if (bytes == null) { + throw new TypeError("C pointer missing bytes indication."); + } + } else if (isTypedArray(candidate)) { + // Typed Array + ptr = Type.uint8_t.out_ptr.implementation(candidate.buffer); + if (bytes == null) { + bytes = candidate.byteLength; + } else if (candidate.byteLength < bytes) { + throw new TypeError("Buffer is too short. I need at least " + + bytes + + " bytes but I have only " + + candidate.byteLength + + "bytes"); + } + } else { + throw new TypeError("Expecting a Typed Array or a C pointer"); + } + return {ptr: ptr, bytes: bytes}; +}; +exports.normalizeToPointer = normalizeToPointer; ///////////////////// OS interactions diff --git a/toolkit/components/osfile/modules/osfile_shared_front.jsm b/toolkit/components/osfile/modules/osfile_shared_front.jsm index 084ef1a776b7..3e1ef9a06fe9 100644 --- a/toolkit/components/osfile/modules/osfile_shared_front.jsm +++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm @@ -82,7 +82,7 @@ AbstractFile.prototype = { * less than |bytes| if the file did not contain that many bytes left. */ readTo: function readTo(buffer, options = {}) { - let {ptr, bytes} = AbstractFile.normalizeToPointer(buffer, options.bytes); + let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes); let pos = 0; while (pos < bytes) { let chunkSize = this._read(ptr, bytes - pos, options); @@ -116,7 +116,7 @@ AbstractFile.prototype = { write: function write(buffer, options = {}) { let {ptr, bytes} = - AbstractFile.normalizeToPointer(buffer, options.bytes || undefined); + SharedAll.normalizeToPointer(buffer, options.bytes || undefined); let pos = 0; while (pos < bytes) { @@ -186,51 +186,6 @@ AbstractFile.openUnique = function openUnique(path, options = {}) { } }; -/** - * Utility function used to normalize a Typed Array or C - * pointer into a uint8_t C pointer. - * - * Future versions might extend this to other data structures. - * - * @param {Typed array | C pointer} candidate The buffer. If - * a C pointer, it must be non-null. - * @param {number} bytes The number of bytes that |candidate| should contain. - * Used for sanity checking if the size of |candidate| can be determined. - * - * @return {ptr:{C pointer}, bytes:number} A C pointer of type uint8_t, - * corresponding to the start of |candidate|. - */ -AbstractFile.normalizeToPointer = function normalizeToPointer(candidate, bytes) { - if (!candidate) { - throw new TypeError("Expecting a Typed Array or a C pointer"); - } - let ptr; - if ("isNull" in candidate) { - if (candidate.isNull()) { - throw new TypeError("Expecting a non-null pointer"); - } - ptr = SharedAll.Type.uint8_t.out_ptr.cast(candidate); - if (bytes == null) { - throw new TypeError("C pointer missing bytes indication."); - } - } else if (SharedAll.isTypedArray(candidate)) { - // Typed Array - ptr = SharedAll.Type.uint8_t.out_ptr.implementation(candidate.buffer); - if (bytes == null) { - bytes = candidate.byteLength; - } else if (candidate.byteLength < bytes) { - throw new TypeError("Buffer is too short. I need at least " + - bytes + - " bytes but I have only " + - candidate.byteLength + - "bytes"); - } - } else { - throw new TypeError("Expecting a Typed Array or a C pointer"); - } - return {ptr: ptr, bytes: bytes}; -}; - /** * Code shared by iterators. */ From b077a39c75aa26a4186026099b8952fe441aaf9c Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Mon, 2 Dec 2013 11:34:46 -0500 Subject: [PATCH 05/67] Bug 854169 - Build the binary support for Lz4.js. r=glandium --- toolkit/components/moz.build | 1 + toolkit/components/workerlz4/moz.build | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 toolkit/components/workerlz4/moz.build diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 55615075f36c..ef01b91ca5aa 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -43,6 +43,7 @@ PARALLEL_DIRS += [ 'viewconfig', 'viewsource', 'workerloader', + 'workerlz4', ] if CONFIG['MOZ_SOCIAL']: diff --git a/toolkit/components/workerlz4/moz.build b/toolkit/components/workerlz4/moz.build new file mode 100644 index 000000000000..3ae13b1e73dc --- /dev/null +++ b/toolkit/components/workerlz4/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JS_MODULES_PATH = 'modules/workers' + +EXTRA_JS_MODULES += [ + 'lz4.js', + 'lz4_internal.js', +] + +SOURCES += [ + 'lz4.cpp', +] + +TEST_DIRS += ['tests'] +XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] + +EXPORT_LIBRARY = True +FINAL_LIBRARY = 'xul' From 3c1e31696fdcc1016c4879adf952741361510b4e Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Fri, 8 Nov 2013 09:16:04 -0500 Subject: [PATCH 06/67] Bug 854169 - Bindings to lz4 for chrome workers. r=froydnj --- toolkit/components/workerlz4/lz4.cpp | 73 ++++++++++ toolkit/components/workerlz4/lz4.js | 136 +++++++++++++++++++ toolkit/components/workerlz4/lz4_internal.js | 58 ++++++++ 3 files changed, 267 insertions(+) create mode 100644 toolkit/components/workerlz4/lz4.cpp create mode 100644 toolkit/components/workerlz4/lz4.js create mode 100644 toolkit/components/workerlz4/lz4_internal.js diff --git a/toolkit/components/workerlz4/lz4.cpp b/toolkit/components/workerlz4/lz4.cpp new file mode 100644 index 000000000000..34d56802525d --- /dev/null +++ b/toolkit/components/workerlz4/lz4.cpp @@ -0,0 +1,73 @@ +/* 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 "mozilla/Compression.h" + +/** + * LZ4 is a very fast byte-wise compression algorithm. + * + * Compared to Google's Snappy it is faster to compress and decompress and + * generally produces output of about the same size. + * + * Compared to zlib it compresses at about 10x the speed, decompresses at about + * 4x the speed and produces output of about 1.5x the size. + * + */ + +using namespace mozilla::Compression; + +/** + * Compresses 'inputSize' bytes from 'source' into 'dest'. + * Destination buffer must be already allocated, + * and must be sized to handle worst cases situations (input data not compressible) + * Worst case size evaluation is provided by function LZ4_compressBound() + * + * @param inputSize is the input size. Max supported value is ~1.9GB + * @param return the number of bytes written in buffer dest + */ +extern "C" MOZ_EXPORT size_t +workerlz4_compress(const char* source, size_t inputSize, char* dest) { + return LZ4::compress(source, inputSize, dest); +} + +/** + * If the source stream is malformed, the function will stop decoding + * and return a negative result, indicating the byte position of the + * faulty instruction + * + * This function never writes outside of provided buffers, and never + * modifies input buffer. + * + * note : destination buffer must be already allocated. + * its size must be a minimum of 'outputSize' bytes. + * @param outputSize is the output size, therefore the original size + * @return true/false + */ +extern "C" MOZ_EXPORT int +workerlz4_decompress(const char* source, size_t inputSize, + char* dest, size_t maxOutputSize, + size_t *bytesOutput) { + return LZ4::decompress(source, inputSize, + dest, maxOutputSize, + bytesOutput); +} + + +/* + Provides the maximum size that LZ4 may output in a "worst case" + scenario (input data not compressible) primarily useful for memory + allocation of output buffer. + note : this function is limited by "int" range (2^31-1) + + @param inputSize is the input size. Max supported value is ~1.9GB + @return maximum output size in a "worst case" scenario +*/ +extern "C" MOZ_EXPORT size_t +workerlz4_maxCompressedSize(size_t inputSize) +{ + return LZ4::maxCompressedSize(inputSize); +} + + + diff --git a/toolkit/components/workerlz4/lz4.js b/toolkit/components/workerlz4/lz4.js new file mode 100644 index 000000000000..ffca7ca816c0 --- /dev/null +++ b/toolkit/components/workerlz4/lz4.js @@ -0,0 +1,136 @@ +/* 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"; + +if (typeof Components != "undefined") { + throw new Error("This file is meant to be loaded in a worker"); +} +if (!module || !exports) { + throw new Error("Please load this module with require()"); +} + +const SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); +const Internals = require("resource://gre/modules/workers/lz4_internal.js"); + +const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0" + +const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size; + +const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER; + +const EXPECTED_HEADER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, HEADER_SIZE); +const EXPECTED_SIZE_BUFFER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, BYTES_IN_SIZE_HEADER); + +/** + * An error during (de)compression + * + * @param {string} operation The name of the operation ("compress", "decompress") + * @param {string} reason A reason to be used when matching errors. Must start + * with "because", e.g. "becauseInvalidContent". + * @param {string} message A human-readable message. + */ +function LZError(operation, reason, message) { + SharedAll.OSError.call(this); + this.operation = operation; + this[reason] = true; + this.message = message; +} +LZError.prototype = Object.create(SharedAll.OSError); +LZError.prototype.toString = function toString() { + return this.message; +}; +exports.Error = LZError; + +/** + * Compress a block to a form suitable for writing to disk. + * + * Compatibility note: For the moment, we are basing our code on lz4 + * 1.3, which does not specify a *file* format. Therefore, we define + * our own format. Once lz4 defines a complete file format, we will + * migrate both |compressFileContent| and |decompressFileContent| to this file + * format. For backwards-compatibility, |decompressFileContent| will however + * keep the ability to decompress files provided with older versions of + * |compressFileContent|. + * + * Compressed files have the following layout: + * + * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress | + * + * @param {TypedArray|void*} buffer The buffer to write to the disk. + * @param {object=} options An object that may contain the following fields: + * - {number} bytes The number of bytes to read from |buffer|. If |buffer| + * is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If + * |buffer| is a |void*|, |bytes| MUST be provided. + * @return {Uint8Array} An array of bytes suitable for being written to the + * disk. + */ +function compressFileContent(array, options = {}) { + // Prepare the output array + let inputBytes; + if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) { + inputBytes = array.byteLength; + } else if (options && options.bytes) { + inputBytes = options.bytes; + } else { + throw new TypeError("compressFileContent requires a size"); + } + let maxCompressedSize = Internals.maxCompressedSize(inputBytes); + let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize); + + // Compress to output array + let payload = new Uint8Array(outputArray.buffer, outputArray.byteOffset + HEADER_SIZE); + let compressedSize = Internals.compress(array, inputBytes, payload); + + // Add headers + outputArray.set(MAGIC_NUMBER); + let view = new DataView(outputArray.buffer); + view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true); + + return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize); +} +exports.compressFileContent = compressFileContent; + +function decompressFileContent(array, options = {}) { + let {ptr, bytes} = SharedAll.normalizeToPointer(array, options.bytes); + if (bytes < HEADER_SIZE) { + throw new LZError("decompress", "becauseLZNoHeader", "Buffer is too short (no header)"); + } + + // Read headers + let expectMagicNumber = ctypes.cast(ptr, EXPECTED_HEADER_TYPE.ptr).contents; + for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) { + if (expectMagicNumber[i] != MAGIC_NUMBER[i]) { + throw new LZError("decompress", "becauseLZWrongMagicNumber", "Invalid header (no magic number"); + } + } + + let sizeBuf = + ctypes.cast( + SharedAll.offsetBy(ptr, MAGIC_NUMBER.byteLength), + EXPECTED_SIZE_BUFFER_TYPE.ptr).contents; + let expectDecompressedSize = + sizeBuf[0] + (sizeBuf[1] << 8) + (sizeBuf[2] << 16) + (sizeBuf[3] << 24); + if (expectDecompressedSize == 0) { + // The underlying algorithm cannot handle a size of 0 + return new Uint8Array(0); + } + + // Prepare the input buffer + let inputPtr = SharedAll.offsetBy(ptr, HEADER_SIZE); + + // Prepare the output buffer + let outputBuffer = new Uint8Array(expectDecompressedSize); + let decompressedBytes = (new SharedAll.Type.size_t.implementation(0)); + + // Decompress + let success = Internals.decompress(inputPtr, bytes - HEADER_SIZE, + outputBuffer, outputBuffer.byteLength, + decompressedBytes.address()); + if (!success) { + throw new LZError("decompress", "becauseLZInvalidContent", "Invalid content:Decompression stopped at " + decompressedBytes.value); + } + return new Uint8Array(outputBuffer.buffer, outputBuffer.byteOffset, decompressedBytes.value); +} +exports.decompressFileContent = decompressFileContent; diff --git a/toolkit/components/workerlz4/lz4_internal.js b/toolkit/components/workerlz4/lz4_internal.js new file mode 100644 index 000000000000..80758913d398 --- /dev/null +++ b/toolkit/components/workerlz4/lz4_internal.js @@ -0,0 +1,58 @@ +/* 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"; + +if (typeof Components != "undefined") { + throw new Error("This file is meant to be loaded in a worker"); +} +if (!module || !exports) { + throw new Error("Please load this module with require()"); +} + +let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); +let libxul = ctypes.open(SharedAll.Constants.Path.libxul); +let declareLazyFII = SharedAll.declareLazyFFI; +let Type = SharedAll.Type; + +let Primitives = {}; + +declareLazyFII(Primitives, "compress", libxul, + "workerlz4_compress", + null, + /*return*/ Type.size_t, + /*const source*/ Type.void_t.in_ptr, + /*inputSize*/ Type.size_t, + /*dest*/ Type.void_t.out_ptr +); + +declareLazyFII(Primitives, "decompress", libxul, + "workerlz4_decompress", + null, + /*return*/ Type.int, + /*const source*/ Type.void_t.in_ptr, + /*inputSize*/ Type.size_t, + /*dest*/ Type.void_t.out_ptr, + /*maxOutputSize*/ Type.size_t, + /*actualOutputSize*/ Type.size_t.out_ptr +); + +declareLazyFII(Primitives, "maxCompressedSize", libxul, + "workerlz4_maxCompressedSize", + null, + /*return*/ Type.size_t, + /*inputSize*/ Type.size_t +); + +module.exports = { + get compress() { + return Primitives.compress; + }, + get decompress() { + return Primitives.decompress; + }, + get maxCompressedSize() { + return Primitives.maxCompressedSize; + } +}; From b76b473ab5fe260fbdb6f657f7db01e4d6d7d0c3 Mon Sep 17 00:00:00 2001 From: David Rajchenbach-Teller Date: Fri, 8 Nov 2013 09:16:04 -0500 Subject: [PATCH 07/67] Bug 854169 - Tests for the Lz4 worker module. r=froydnj --- .../tests/xpcshell/data/chrome.manifest | 1 + .../tests/xpcshell/data/compression.lz | Bin 0 -> 23 bytes .../tests/xpcshell/data/worker_lz4.js | 146 ++++++++++++++++++ .../workerlz4/tests/xpcshell/test_lz4.js | 43 ++++++ .../workerlz4/tests/xpcshell/xpcshell.ini | 9 ++ 5 files changed, 199 insertions(+) create mode 100644 toolkit/components/workerlz4/tests/xpcshell/data/chrome.manifest create mode 100644 toolkit/components/workerlz4/tests/xpcshell/data/compression.lz create mode 100644 toolkit/components/workerlz4/tests/xpcshell/data/worker_lz4.js create mode 100644 toolkit/components/workerlz4/tests/xpcshell/test_lz4.js create mode 100644 toolkit/components/workerlz4/tests/xpcshell/xpcshell.ini diff --git a/toolkit/components/workerlz4/tests/xpcshell/data/chrome.manifest b/toolkit/components/workerlz4/tests/xpcshell/data/chrome.manifest new file mode 100644 index 000000000000..e2f9a9d8ef4f --- /dev/null +++ b/toolkit/components/workerlz4/tests/xpcshell/data/chrome.manifest @@ -0,0 +1 @@ +content test_lz4 ./ diff --git a/toolkit/components/workerlz4/tests/xpcshell/data/compression.lz b/toolkit/components/workerlz4/tests/xpcshell/data/compression.lz new file mode 100644 index 0000000000000000000000000000000000000000..a354edc03673f2d3fc3b9f07f9f39d1bd59bf656 GIT binary patch literal 23 ecmd1JukxufF<{_gU|?9_k(!f}ucMGtWdZ Date: Fri, 8 Nov 2013 09:16:05 -0500 Subject: [PATCH 08/67] Bug 854169 - OS.File.{read, writeAtomic} support for lz4. r=froydnj --- .../osfile/modules/osfile_async_front.jsm | 7 ++- .../osfile/modules/osfile_async_worker.js | 3 +- .../osfile/modules/osfile_shared_front.jsm | 32 ++++++++++-- .../osfile/tests/xpcshell/test_compression.js | 52 +++++++++++++++++++ .../osfile/tests/xpcshell/xpcshell.ini | 1 + 5 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 toolkit/components/osfile/tests/xpcshell/test_compression.js diff --git a/toolkit/components/osfile/modules/osfile_async_front.jsm b/toolkit/components/osfile/modules/osfile_async_front.jsm index 062e1a621409..9f2d6b409d14 100644 --- a/toolkit/components/osfile/modules/osfile_async_front.jsm +++ b/toolkit/components/osfile/modules/osfile_async_front.jsm @@ -730,18 +730,21 @@ File.makeDir = function makeDir(path, options) { * * @param {string} path The path to the file. * @param {number=} bytes Optionally, an upper bound to the number of bytes - * to read. + * to read. DEPRECATED - please use options.bytes instead. * @param {JSON} options Additional options. * - {boolean} sequential A flag that triggers a population of the page cache * with data from a file so that subsequent reads from that file would not * block on disk I/O. If |true| or unspecified, inform the system that the * contents of the file will be read in order. Otherwise, make no such * assumption. |true| by default. + * - {number} bytes An upper bound to the number of bytes to read. + * - {string} compression If "lz4" and if the file is compressed using the lz4 + * compression algorithm, decompress the file contents on the fly. * * @resolves {Uint8Array} A buffer holding the bytes * read from the file. */ -File.read = function read(path, bytes, options) { +File.read = function read(path, bytes, options = {}) { let promise = Scheduler.post("read", [Type.path.toMsg(path), bytes, options], path); return promise.then( diff --git a/toolkit/components/osfile/modules/osfile_async_worker.js b/toolkit/components/osfile/modules/osfile_async_worker.js index b1ae1e093a52..6f9cc0cd11a5 100644 --- a/toolkit/components/osfile/modules/osfile_async_worker.js +++ b/toolkit/components/osfile/modules/osfile_async_worker.js @@ -93,10 +93,11 @@ if (this.Components) { // instances of |OS.File.Error|) self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs}); } else { - LOG("Sending back regular error", exn, exn.stack, "id is", id); // Other exceptions do not, and should be propagated through DOM's // built-in mechanism for uncaught errors, although this mechanism // may lose interesting information. + LOG("Sending back regular error", exn, exn.stack, "id is", id); + throw exn; } }; diff --git a/toolkit/components/osfile/modules/osfile_shared_front.jsm b/toolkit/components/osfile/modules/osfile_shared_front.jsm index 3e1ef9a06fe9..7d18bc1ebc00 100644 --- a/toolkit/components/osfile/modules/osfile_shared_front.jsm +++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm @@ -16,7 +16,8 @@ if (typeof Components != "undefined") { let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); - +let Lz4 = + require("resource://gre/modules/workers/lz4.js"); let LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end"); let clone = SharedAll.clone; @@ -310,16 +311,29 @@ AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) { * * @param {string} path The path to the file. * @param {number=} bytes Optionally, an upper bound to the number of bytes - * to read. - * @param {JSON} options Optionally contains additional options. + * to read. DEPRECATED - please use options.bytes instead. + * @param {object=} options Optionally, an object with some of the following + * fields: + * - {number} bytes An upper bound to the number of bytes to read. + * - {string} compression If "lz4" and if the file is compressed using the lz4 + * compression algorithm, decompress the file contents on the fly. * * @return {Uint8Array} A buffer holding the bytes * and the number of bytes read from the file. */ AbstractFile.read = function read(path, bytes, options = {}) { + if (bytes && typeof bytes == "object") { + options = bytes; + bytes = options.bytes || null; + } let file = exports.OS.File.open(path); try { - return file.read(bytes, options); + let buffer = file.read(bytes, options); + if (options.compression == "lz4") { + return Lz4.decompressFileContent(buffer, options); + } else { + return buffer; + } } finally { file.close(); } @@ -360,6 +374,10 @@ AbstractFile.read = function read(path, bytes, options = {}) { * if the system shuts down improperly (typically due to a kernel freeze * or a power failure) or if the device is disconnected before the buffer * is flushed, the file has more chances of not being corrupted. + * - {string} compression - If empty or unspecified, do not compress the file. + * If "lz4", compress the contents of the file atomically using lz4. For the + * time being, the container format is specific to Mozilla and cannot be read + * by means other than OS.File.read(..., { compression: "lz4"}) * * @return {number} The number of bytes actually written. */ @@ -381,6 +399,12 @@ AbstractFile.writeAtomic = buffer = new TextEncoder(encoding).encode(buffer); } + if (options.compression == "lz4") { + buffer = Lz4.compressFileContent(buffer, options); + options = Object.create(options); + options.bytes = buffer.byteLength; + } + let bytesWritten = 0; if (!options.tmpPath) { diff --git a/toolkit/components/osfile/tests/xpcshell/test_compression.js b/toolkit/components/osfile/tests/xpcshell/test_compression.js new file mode 100644 index 000000000000..bc0167c52b71 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_compression.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +add_task(function test_compress_lz4() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, "compression.lz"); + let array = new Uint8Array(1024); + for (let i = 0; i < array.byteLength; ++i) { + array[i] = i; + } + + do_print("Writing data with lz4 compression"); + let bytes = yield OS.File.writeAtomic(path, array, { compression: "lz4" }); + do_print("Compressed " + array.byteLength + " bytes into " + bytes); + + do_print("Reading back with lz4 decompression"); + let decompressed = yield OS.File.read(path, { compression: "lz4" }); + do_print("Decompressed into " + decompressed.byteLength + " bytes"); + do_check_eq(Array.prototype.join.call(array), Array.prototype.join.call(decompressed)); +}); + +add_task(function test_uncompressed() { + do_print("Writing data without compression"); + let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_compression.tmp"); + let array = new Uint8Array(1024); + for (let i = 0; i < array.byteLength; ++i) { + array[i] = i; + } + let bytes = yield OS.File.writeAtomic(path, array); // No compression + + let exn; + // Force decompression, reading should fail + try { + yield OS.File.read(path, { compression: "lz4" }); + } catch (ex) { + exn = ex; + } + do_check_true(!!exn); + do_check_true(exn.message.indexOf("Invalid header") != -1); +}); + +add_task(function() { + do_test_finished(); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini index 19646c017c28..6fab8a0d6296 100644 --- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini @@ -22,3 +22,4 @@ tail = [test_shutdown.js] [test_unique.js] [test_open.js] +[test_compression.js] From ccf0bfae5295afa8126de1d650e6c4e0fc54c910 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 00:25:23 -0800 Subject: [PATCH 09/67] Bumping gaia.json for 1 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/a8674008468e Author: Evyatar Amitay Desc: Revert "[Bug 944026] Make sure installed apps are the native size [r=amirn]" This reverts commit ea7938ee280423f1b8523661be0c8ccc01f9eeb3. --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 8ef21ce90b10..bdefa19cbdaa 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "cf23b263b4bbf1024210350f24c7e6eb5872a4be", + "revision": "a8674008468e815eda97b9501862f721e4ba4df7", "repo_path": "/integration/gaia-central" } From c2d4ccccd7c3be863587f90a8f41773b963bbe1c Mon Sep 17 00:00:00 2001 From: Ben Tian Date: Fri, 29 Nov 2013 14:18:04 +0800 Subject: [PATCH 10/67] Bug 915533 - Patch 1/2: [bludroid OPP] Move files, r=echou --- .../{ => bluedroid}/BluetoothOppManager.cpp | 0 .../{ => bluedroid}/BluetoothOppManager.h | 0 .../{ => bluedroid}/BluetoothSocket.cpp | 0 .../{ => bluedroid}/BluetoothSocket.h | 0 dom/bluetooth/bluez/BluetoothOppManager.cpp | 1587 +++++++++++++++++ dom/bluetooth/bluez/BluetoothOppManager.h | 228 +++ dom/bluetooth/bluez/BluetoothSocket.cpp | 98 + dom/bluetooth/bluez/BluetoothSocket.h | 52 + .../BluetoothUnixSocketConnector.cpp | 0 .../BluetoothUnixSocketConnector.h | 0 dom/bluetooth/moz.build | 8 +- 11 files changed, 1970 insertions(+), 3 deletions(-) rename dom/bluetooth/{ => bluedroid}/BluetoothOppManager.cpp (100%) rename dom/bluetooth/{ => bluedroid}/BluetoothOppManager.h (100%) rename dom/bluetooth/{ => bluedroid}/BluetoothSocket.cpp (100%) rename dom/bluetooth/{ => bluedroid}/BluetoothSocket.h (100%) create mode 100644 dom/bluetooth/bluez/BluetoothOppManager.cpp create mode 100644 dom/bluetooth/bluez/BluetoothOppManager.h create mode 100644 dom/bluetooth/bluez/BluetoothSocket.cpp create mode 100644 dom/bluetooth/bluez/BluetoothSocket.h rename dom/bluetooth/{ => bluez}/BluetoothUnixSocketConnector.cpp (100%) rename dom/bluetooth/{ => bluez}/BluetoothUnixSocketConnector.h (100%) diff --git a/dom/bluetooth/BluetoothOppManager.cpp b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp similarity index 100% rename from dom/bluetooth/BluetoothOppManager.cpp rename to dom/bluetooth/bluedroid/BluetoothOppManager.cpp diff --git a/dom/bluetooth/BluetoothOppManager.h b/dom/bluetooth/bluedroid/BluetoothOppManager.h similarity index 100% rename from dom/bluetooth/BluetoothOppManager.h rename to dom/bluetooth/bluedroid/BluetoothOppManager.h diff --git a/dom/bluetooth/BluetoothSocket.cpp b/dom/bluetooth/bluedroid/BluetoothSocket.cpp similarity index 100% rename from dom/bluetooth/BluetoothSocket.cpp rename to dom/bluetooth/bluedroid/BluetoothSocket.cpp diff --git a/dom/bluetooth/BluetoothSocket.h b/dom/bluetooth/bluedroid/BluetoothSocket.h similarity index 100% rename from dom/bluetooth/BluetoothSocket.h rename to dom/bluetooth/bluedroid/BluetoothSocket.h diff --git a/dom/bluetooth/bluez/BluetoothOppManager.cpp b/dom/bluetooth/bluez/BluetoothOppManager.cpp new file mode 100644 index 000000000000..9d667530cf5c --- /dev/null +++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp @@ -0,0 +1,1587 @@ +/* -*- 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 "base/basictypes.h" +#include "BluetoothOppManager.h" + +#include "BluetoothService.h" +#include "BluetoothSocket.h" +#include "BluetoothUtils.h" +#include "BluetoothUuid.h" +#include "ObexBase.h" + +#include "mozilla/dom/bluetooth/BluetoothTypes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "nsAutoPtr.h" +#include "nsCExternalHandlerService.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIDOMFile.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIMIMEService.h" +#include "nsIOutputStream.h" +#include "nsIVolumeService.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" + +#define TARGET_SUBDIR "Download/Bluetooth/" + +USING_BLUETOOTH_NAMESPACE +using namespace mozilla; +using namespace mozilla::ipc; + +namespace { +// Sending system message "bluetooth-opp-update-progress" every 50kb +static const uint32_t kUpdateProgressBase = 50 * 1024; + +/* + * The format of the header of an PUT request is + * [opcode:1][packet length:2][headerId:1][header length:2] + */ +static const uint32_t kPutRequestHeaderSize = 6; + +StaticRefPtr sBluetoothOppManager; +static bool sInShutdown = false; +} + +class mozilla::dom::bluetooth::SendFileBatch { +public: + SendFileBatch(const nsAString& aDeviceAddress, BlobParent* aActor) + : mDeviceAddress(aDeviceAddress) + { + mBlobs.AppendElement(aActor->GetBlob().get()); + } + + nsString mDeviceAddress; + nsCOMArray mBlobs; +}; + +NS_IMETHODIMP +BluetoothOppManager::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + MOZ_ASSERT(sBluetoothOppManager); + + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + HandleShutdown(); + return NS_OK; + } + + MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!"); + return NS_ERROR_UNEXPECTED; +} + +class SendSocketDataTask : public nsRunnable +{ +public: + SendSocketDataTask(uint8_t* aStream, uint32_t aSize) + : mStream(aStream) + , mSize(aSize) + { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + sBluetoothOppManager->SendPutRequest(mStream, mSize); + + return NS_OK; + } + +private: + nsAutoArrayPtr mStream; + uint32_t mSize; +}; + +class ReadFileTask : public nsRunnable +{ +public: + ReadFileTask(nsIInputStream* aInputStream, + uint32_t aRemoteMaxPacketSize) : mInputStream(aInputStream) + { + MOZ_ASSERT(NS_IsMainThread()); + + mAvailablePacketSize = aRemoteMaxPacketSize - kPutRequestHeaderSize; + } + + NS_IMETHOD Run() + { + MOZ_ASSERT(!NS_IsMainThread()); + + uint32_t numRead; + nsAutoArrayPtr buf(new char[mAvailablePacketSize]); + + // function inputstream->Read() only works on non-main thread + nsresult rv = mInputStream->Read(buf, mAvailablePacketSize, &numRead); + if (NS_FAILED(rv)) { + // Needs error handling here + BT_WARNING("Failed to read from input stream"); + return NS_ERROR_FAILURE; + } + + if (numRead > 0) { + sBluetoothOppManager->CheckPutFinal(numRead); + + nsRefPtr task = + new SendSocketDataTask((uint8_t*)buf.forget(), numRead); + if (NS_FAILED(NS_DispatchToMainThread(task))) { + BT_WARNING("Failed to dispatch to main thread!"); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; + }; + +private: + nsCOMPtr mInputStream; + uint32_t mAvailablePacketSize; +}; + +class CloseSocketTask : public Task +{ +public: + CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket) + { + MOZ_ASSERT(aSocket); + } + + void Run() MOZ_OVERRIDE + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mSocket->GetConnectionStatus() == + SocketConnectionStatus::SOCKET_CONNECTED) { + mSocket->Disconnect(); + } + } + +private: + nsRefPtr mSocket; +}; + +BluetoothOppManager::BluetoothOppManager() : mConnected(false) + , mRemoteObexVersion(0) + , mRemoteConnectionFlags(0) + , mRemoteMaxPacketLength(0) + , mLastCommand(0) + , mPacketLength(0) + , mPacketReceivedLength(0) + , mBodySegmentLength(0) + , mAbortFlag(false) + , mNewFileFlag(false) + , mPutFinalFlag(false) + , mSendTransferCompleteFlag(false) + , mSuccessFlag(false) + , mIsServer(true) + , mWaitingForConfirmationFlag(false) + , mFileLength(0) + , mSentFileLength(0) + , mWaitingToSendPutFinal(false) + , mCurrentBlobIndex(-1) +{ + mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); +} + +BluetoothOppManager::~BluetoothOppManager() +{ + nsCOMPtr obs = services::GetObserverService(); + NS_ENSURE_TRUE_VOID(obs); + if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { + BT_WARNING("Failed to remove shutdown observer!"); + } +} + +bool +BluetoothOppManager::Init() +{ + nsCOMPtr obs = services::GetObserverService(); + NS_ENSURE_TRUE(obs, false); + if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) { + BT_WARNING("Failed to add shutdown observer!"); + return false; + } + + Listen(); + + return true; +} + +//static +BluetoothOppManager* +BluetoothOppManager::Get() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If sBluetoothOppManager already exists, exit early + if (sBluetoothOppManager) { + return sBluetoothOppManager; + } + + // If we're in shutdown, don't create a new instance + NS_ENSURE_FALSE(sInShutdown, nullptr); + + // Create a new instance, register, and return + BluetoothOppManager *manager = new BluetoothOppManager(); + NS_ENSURE_TRUE(manager->Init(), nullptr); + + sBluetoothOppManager = manager; + return sBluetoothOppManager; +} + +void +BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Stop listening because currently we only support one connection at a time. + if (mRfcommSocket) { + mRfcommSocket->Disconnect(); + mRfcommSocket = nullptr; + } + + if (mL2capSocket) { + mL2capSocket->Disconnect(); + mL2capSocket = nullptr; + } + + mIsServer = false; + + BluetoothService* bs = BluetoothService::Get(); + if (!bs || sInShutdown || mSocket) { + OnSocketConnectError(mSocket); + return; + } + + mNeedsUpdatingSdpRecords = true; + + nsString uuid; + BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid); + + if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) { + OnSocketConnectError(mSocket); + return; + } + + mSocket = + new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); +} + +void +BluetoothOppManager::HandleShutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + sInShutdown = true; + + if (mSocket) { + mSocket->Disconnect(); + mSocket = nullptr; + } + if (mRfcommSocket) { + mRfcommSocket->Disconnect(); + mRfcommSocket = nullptr; + } + if (mL2capSocket) { + mL2capSocket->Disconnect(); + mL2capSocket = nullptr; + } + sBluetoothOppManager = nullptr; +} + +bool +BluetoothOppManager::Listen() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mSocket) { + BT_WARNING("mSocket exists. Failed to listen."); + return false; + } + + if (!mRfcommSocket) { + mRfcommSocket = + new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); + + if (!mRfcommSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) { + BT_WARNING("[OPP] Can't listen on RFCOMM socket!"); + mRfcommSocket = nullptr; + return false; + } + } + + if (!mL2capSocket) { + mL2capSocket = + new BluetoothSocket(this, BluetoothSocketType::EL2CAP, true, true); + + if (!mL2capSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH_L2CAP)) { + BT_WARNING("[OPP] Can't listen on L2CAP socket!"); + mRfcommSocket->Disconnect(); + mRfcommSocket = nullptr; + mL2capSocket = nullptr; + return false; + } + } + + mIsServer = true; + + return true; +} + +void +BluetoothOppManager::StartSendingNextFile() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(!mBatches.IsEmpty()); + MOZ_ASSERT(mBatches[0].mBlobs.Length() > mCurrentBlobIndex + 1); + + mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; + + // Before sending content, we have to send a header including + // information such as file name, file length and content type. + ExtractBlobHeaders(); + StartFileTransfer(); + + if (mCurrentBlobIndex == 0) { + // We may have more than one file waiting for transferring, but only one + // CONNECT request would be sent. Therefore check if this is the very first + // file at the head of queue. + SendConnectRequest(); + } else { + SendPutHeaderRequest(mFileName, mFileLength); + AfterFirstPut(); + } +} + +bool +BluetoothOppManager::SendFile(const nsAString& aDeviceAddress, + BlobParent* aActor) +{ + MOZ_ASSERT(NS_IsMainThread()); + + AppendBlobToSend(aDeviceAddress, aActor); + if (!mSocket) { + ProcessNextBatch(); + } + + return true; +} + +void +BluetoothOppManager::AppendBlobToSend(const nsAString& aDeviceAddress, + BlobParent* aActor) +{ + MOZ_ASSERT(NS_IsMainThread()); + + int indexTail = mBatches.Length() - 1; + + /** + * Create a new batch if + * - mBatches is empty, or + * - aDeviceAddress differs from mDeviceAddress of the last batch + */ + if (mBatches.IsEmpty() || + aDeviceAddress != mBatches[indexTail].mDeviceAddress) { + SendFileBatch batch(aDeviceAddress, aActor); + mBatches.AppendElement(batch); + } else { + mBatches[indexTail].mBlobs.AppendElement(aActor->GetBlob().get()); + } +} + +void +BluetoothOppManager::DiscardBlobsToSend() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!mBatches.IsEmpty()); + MOZ_ASSERT(!mIsServer); + + int length = (int) mBatches[0].mBlobs.Length(); + while (length > mCurrentBlobIndex + 1) { + mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex]; + + BT_LOGR("%s: idx %d", __FUNCTION__, mCurrentBlobIndex); + ExtractBlobHeaders(); + StartFileTransfer(); + FileTransferComplete(); + } +} + +bool +BluetoothOppManager::ProcessNextBatch() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Remove the processed batch. + // A batch is processed if we've incremented mCurrentBlobIndex for it. + if (mCurrentBlobIndex >= 0) { + ClearQueue(); + mBatches.RemoveElementAt(0); + BT_LOGR("%s: REMOVE. %d remaining", __FUNCTION__, mBatches.Length()); + } + + // Process the next batch + if (!mBatches.IsEmpty()) { + ConnectInternal(mBatches[0].mDeviceAddress); + return true; + } + + // No more batch to process + return false; +} + +void +BluetoothOppManager::ClearQueue() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!mIsServer); + MOZ_ASSERT(!mBatches.IsEmpty()); + MOZ_ASSERT(!mBatches[0].mBlobs.IsEmpty()); + + mCurrentBlobIndex = -1; + mBlob = nullptr; + mBatches[0].mBlobs.Clear(); +} + +bool +BluetoothOppManager::StopSendingFile() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mIsServer) { + mAbortFlag = true; + } else if (mSocket) { + mSocket->Disconnect(); + } else { + BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__); + } + + return true; +} + +bool +BluetoothOppManager::ConfirmReceivingFile(bool aConfirm) +{ + NS_ENSURE_TRUE(mConnected, false); + NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false); + + MOZ_ASSERT(mPacketReceivedLength == 0); + + mWaitingForConfirmationFlag = false; + + // For the first packet of first file + bool success = false; + if (aConfirm) { + StartFileTransfer(); + if (CreateFile()) { + success = WriteToFile(mBodySegment.get(), mBodySegmentLength); + } + } + + if (success && mPutFinalFlag) { + mSuccessFlag = true; + FileTransferComplete(); + NotifyAboutFileChange(); + } + + ReplyToPut(mPutFinalFlag, success); + return true; +} + +void +BluetoothOppManager::AfterFirstPut() +{ + mUpdateProgressCounter = 1; + mPutFinalFlag = false; + mPacketReceivedLength = 0; + mSentFileLength = 0; + mWaitingToSendPutFinal = false; + mSuccessFlag = false; + mBodySegmentLength = 0; +} + +void +BluetoothOppManager::AfterOppConnected() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mConnected = true; + mAbortFlag = false; + mWaitingForConfirmationFlag = true; + AfterFirstPut(); + // Get a mount lock to prevent the sdcard from being shared with + // the PC while we're doing a OPP file transfer. After OPP transaction + // were done, the mount lock will be freed. + if (!AcquireSdcardMountLock()) { + // If we fail to get a mount lock, abort this transaction + // Directly sending disconnect-request is better than abort-request + BT_WARNING("BluetoothOPPManager couldn't get a mount lock!"); + + MOZ_ASSERT(mSocket); + mSocket->Disconnect(); + } +} + +void +BluetoothOppManager::AfterOppDisconnected() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mConnected = false; + mLastCommand = 0; + mPacketReceivedLength = 0; + mDsFile = nullptr; + + // We can't reset mSuccessFlag here since this function may be called + // before we send system message of transfer complete + // mSuccessFlag = false; + + if (mInputStream) { + mInputStream->Close(); + mInputStream = nullptr; + } + + if (mOutputStream) { + mOutputStream->Close(); + mOutputStream = nullptr; + } + + if (mReadFileThread) { + mReadFileThread->Shutdown(); + mReadFileThread = nullptr; + } + // Release the Mount lock if file transfer completed + if (mMountLock) { + // The mount lock will be implicitly unlocked + mMountLock = nullptr; + } +} + +void +BluetoothOppManager::DeleteReceivedFile() +{ + if (mOutputStream) { + mOutputStream->Close(); + mOutputStream = nullptr; + } + + if (mDsFile && mDsFile->mFile) { + mDsFile->mFile->Remove(false); + mDsFile = nullptr; + } +} + +bool +BluetoothOppManager::CreateFile() +{ + MOZ_ASSERT(mPacketReceivedLength == mPacketLength); + + nsString path; + path.AssignLiteral(TARGET_SUBDIR); + path.Append(mFileName); + + mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_TRUE(mDsFile, false); + + nsCOMPtr f; + mDsFile->mFile->Clone(getter_AddRefs(f)); + + /* + * The function CreateUnique() may create a file with a different file + * name from the original mFileName. Therefore we have to retrieve + * the file name again. + */ + f->GetLeafName(mFileName); + + NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f); + NS_ENSURE_TRUE(mOutputStream, false); + + return true; +} + +bool +BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength) +{ + NS_ENSURE_TRUE(mOutputStream, false); + + uint32_t wrote = 0; + mOutputStream->Write((const char*)aData, aDataLength, &wrote); + NS_ENSURE_TRUE(aDataLength == (int) wrote, false); + + return true; +} + +// Virtual function of class SocketConsumer +void +BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader) +{ + if (aHeader.Has(ObexHeaderId::Name)) { + aHeader.GetName(mFileName); + } + + if (aHeader.Has(ObexHeaderId::Type)) { + aHeader.GetContentType(mContentType); + } + + if (aHeader.Has(ObexHeaderId::Length)) { + aHeader.GetLength(&mFileLength); + } + + if (aHeader.Has(ObexHeaderId::Body) || + aHeader.Has(ObexHeaderId::EndOfBody)) { + uint8_t* bodyPtr; + aHeader.GetBody(&bodyPtr); + mBodySegment = bodyPtr; + + aHeader.GetBodyLength(&mBodySegmentLength); + } +} + +bool +BluetoothOppManager::ExtractBlobHeaders() +{ + RetrieveSentFileName(); + + nsresult rv = mBlob->GetType(mContentType); + if (NS_FAILED(rv)) { + BT_WARNING("Can't get content type"); + SendDisconnectRequest(); + return false; + } + + uint64_t fileLength; + rv = mBlob->GetSize(&fileLength); + if (NS_FAILED(rv)) { + BT_WARNING("Can't get file size"); + SendDisconnectRequest(); + return false; + } + + // Currently we keep the size of files which were sent/received via + // Bluetooth not exceed UINT32_MAX because the Length header in OBEX + // is only 4-byte long. Although it is possible to transfer a file + // larger than UINT32_MAX, it needs to parse another OBEX Header + // and I would like to leave it as a feature. + if (fileLength > (uint64_t)UINT32_MAX) { + BT_WARNING("The file size is too large for now"); + SendDisconnectRequest(); + return false; + } + + mFileLength = fileLength; + rv = NS_NewThread(getter_AddRefs(mReadFileThread)); + if (NS_FAILED(rv)) { + BT_WARNING("Can't create thread"); + SendDisconnectRequest(); + return false; + } + + return true; +} + +void +BluetoothOppManager::RetrieveSentFileName() +{ + mFileName.Truncate(); + + nsCOMPtr file = do_QueryInterface(mBlob); + if (file) { + file->GetName(mFileName); + } + + /** + * We try our best to get the file extention to avoid interoperability issues. + * However, once we found that we are unable to get suitable extension or + * information about the content type, sending a pre-defined file name without + * extension would be fine. + */ + if (mFileName.IsEmpty()) { + mFileName.AssignLiteral("Unknown"); + } + + int32_t offset = mFileName.RFindChar('/'); + if (offset != kNotFound) { + mFileName = Substring(mFileName, offset + 1); + } + + offset = mFileName.RFindChar('.'); + if (offset == kNotFound) { + nsCOMPtr mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID); + + if (mimeSvc) { + nsString mimeType; + mBlob->GetType(mimeType); + + nsCString extension; + nsresult rv = + mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), + EmptyCString(), + extension); + if (NS_SUCCEEDED(rv)) { + mFileName.AppendLiteral("."); + AppendUTF8toUTF16(extension, mFileName); + } + } + } +} + +bool +BluetoothOppManager::IsReservedChar(PRUnichar c) +{ + return (c < 0x0020 || + c == PRUnichar('?') || c == PRUnichar('|') || c == PRUnichar('<') || + c == PRUnichar('>') || c == PRUnichar('"') || c == PRUnichar(':') || + c == PRUnichar('/') || c == PRUnichar('*') || c == PRUnichar('\\')); +} + +void +BluetoothOppManager::ValidateFileName() +{ + int length = mFileName.Length(); + + for (int i = 0; i < length; ++i) { + // Replace reserved char of fat file system with '_' + if (IsReservedChar(mFileName.CharAt(i))) { + mFileName.Replace(i, 1, PRUnichar('_')); + } + } +} + +bool +BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aMessage); + + int frameHeaderLength = 0; + + // See if this is the first part of each Put packet + if (mPacketReceivedLength == 0) { + // Section 3.3.3 "Put", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + frameHeaderLength = 3; + + mPacketLength = ((((int)aMessage->mData[1]) << 8) | aMessage->mData[2]) - + frameHeaderLength; + /** + * A PUT request from remote devices may be divided into multiple parts. + * In other words, one request may need to be received multiple times, + * so here we keep a variable mPacketLeftLength to indicate if current + * PUT request is done. + */ + mReceivedDataBuffer = new uint8_t[mPacketLength]; + mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal); + } + + int dataLength = aMessage->mSize - frameHeaderLength; + + // Check length before memcpy to prevent from memory pollution + if (dataLength < 0 || + mPacketReceivedLength + dataLength > mPacketLength) { + BT_LOGR("%s: Received packet size is unreasonable", __FUNCTION__); + + ReplyToPut(mPutFinalFlag, false); + DeleteReceivedFile(); + FileTransferComplete(); + + return false; + } + + memcpy(mReceivedDataBuffer.get() + mPacketReceivedLength, + &aMessage->mData[frameHeaderLength], dataLength); + + mPacketReceivedLength += dataLength; + + return (mPacketReceivedLength == mPacketLength); +} + +void +BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage) +{ + MOZ_ASSERT(NS_IsMainThread()); + + uint8_t opCode; + int receivedLength = aMessage->mSize; + + if (mPacketReceivedLength > 0) { + opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put; + } else { + opCode = aMessage->mData[0]; + + // When there's a Put packet right after a PutFinal packet, + // which means it's the start point of a new file. + if (mPutFinalFlag && + (opCode == ObexRequestCode::Put || + opCode == ObexRequestCode::PutFinal)) { + mNewFileFlag = true; + AfterFirstPut(); + } + } + + ObexHeaderSet pktHeaders(opCode); + if (opCode == ObexRequestCode::Connect) { + // Section 3.3.1 "Connect", IrOBEX 1.2 + // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] + // [Headers:var] + ParseHeaders(&aMessage->mData[7], + receivedLength - 7, + &pktHeaders); + ReplyToConnect(); + AfterOppConnected(); + } else if (opCode == ObexRequestCode::Abort) { + // Section 3.3.5 "Abort", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + ParseHeaders(&aMessage->mData[3], + receivedLength - 3, + &pktHeaders); + ReplyToDisconnectOrAbort(); + DeleteReceivedFile(); + } else if (opCode == ObexRequestCode::Disconnect) { + // Section 3.3.2 "Disconnect", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + ParseHeaders(&aMessage->mData[3], + receivedLength - 3, + &pktHeaders); + ReplyToDisconnectOrAbort(); + AfterOppDisconnected(); + FileTransferComplete(); + } else if (opCode == ObexRequestCode::Put || + opCode == ObexRequestCode::PutFinal) { + if (!ComposePacket(opCode, aMessage)) { + return; + } + + // A Put packet is received completely + ParseHeaders(mReceivedDataBuffer.get(), mPacketReceivedLength, &pktHeaders); + ExtractPacketHeaders(pktHeaders); + ValidateFileName(); + + mPacketReceivedLength = 0; + + // When we cancel the transfer, delete the file and notify completion + if (mAbortFlag) { + ReplyToPut(mPutFinalFlag, false); + mSentFileLength += mBodySegmentLength; + DeleteReceivedFile(); + FileTransferComplete(); + return; + } + + // Wait until get confirmation from user, then create file and write to it + if (mWaitingForConfirmationFlag) { + ReceivingFileConfirmation(); + mSentFileLength += mBodySegmentLength; + return; + } + + // Already get confirmation from user, create a new file if needed and + // write to output stream + if (mNewFileFlag) { + StartFileTransfer(); + if (!CreateFile()) { + ReplyToPut(mPutFinalFlag, false); + return; + } + mNewFileFlag = false; + } + + if (!WriteToFile(mBodySegment.get(), mBodySegmentLength)) { + ReplyToPut(mPutFinalFlag, false); + return; + } + + ReplyToPut(mPutFinalFlag, true); + + // Send progress update + mSentFileLength += mBodySegmentLength; + if (mSentFileLength > kUpdateProgressBase * mUpdateProgressCounter) { + UpdateProgress(); + mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; + } + + // Success to receive a file and notify completion + if (mPutFinalFlag) { + mSuccessFlag = true; + FileTransferComplete(); + NotifyAboutFileChange(); + } + } else if (opCode == ObexRequestCode::Get || + opCode == ObexRequestCode::GetFinal || + opCode == ObexRequestCode::SetPath) { + ReplyError(ObexResponseCode::BadRequest); + BT_WARNING("Unsupported ObexRequestCode"); + } else { + ReplyError(ObexResponseCode::NotImplemented); + BT_WARNING("Unrecognized ObexRequestCode"); + } +} + +void +BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage) +{ + MOZ_ASSERT(NS_IsMainThread()); + + uint8_t opCode = aMessage->mData[0]; + + // Check response code and send out system message as finished if the response + // code is somehow incorrect. + uint8_t expectedOpCode = ObexResponseCode::Success; + if (mLastCommand == ObexRequestCode::Put) { + expectedOpCode = ObexResponseCode::Continue; + } + + if (opCode != expectedOpCode) { + if (mLastCommand == ObexRequestCode::Put || + mLastCommand == ObexRequestCode::Abort || + mLastCommand == ObexRequestCode::PutFinal) { + SendDisconnectRequest(); + } + nsAutoCString str; + str += "[OPP] 0x"; + str.AppendInt(mLastCommand, 16); + str += " failed"; + BT_WARNING(str.get()); + FileTransferComplete(); + return; + } + + if (mLastCommand == ObexRequestCode::PutFinal) { + mSuccessFlag = true; + FileTransferComplete(); + + if (mInputStream) { + mInputStream->Close(); + mInputStream = nullptr; + } + + if (mCurrentBlobIndex + 1 == (int) mBatches[0].mBlobs.Length()) { + SendDisconnectRequest(); + } else { + StartSendingNextFile(); + } + } else if (mLastCommand == ObexRequestCode::Abort) { + SendDisconnectRequest(); + FileTransferComplete(); + } else if (mLastCommand == ObexRequestCode::Disconnect) { + AfterOppDisconnected(); + // Most devices will directly terminate connection after receiving + // Disconnect request, so we make a delay here. If the socket hasn't been + // disconnected, we will close it. + if (mSocket) { + MessageLoop::current()-> + PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000); + } + } else if (mLastCommand == ObexRequestCode::Connect) { + MOZ_ASSERT(!mFileName.IsEmpty()); + MOZ_ASSERT(mBlob); + + AfterOppConnected(); + + // Keep remote information + mRemoteObexVersion = aMessage->mData[3]; + mRemoteConnectionFlags = aMessage->mData[4]; + mRemoteMaxPacketLength = + (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]); + + SendPutHeaderRequest(mFileName, mFileLength); + } else if (mLastCommand == ObexRequestCode::Put) { + if (mWaitingToSendPutFinal) { + SendPutFinalRequest(); + return; + } + + if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) { + UpdateProgress(); + mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1; + } + + nsresult rv; + if (!mInputStream) { + rv = mBlob->GetInternalStream(getter_AddRefs(mInputStream)); + if (NS_FAILED(rv)) { + BT_WARNING("Can't get internal stream of blob"); + SendDisconnectRequest(); + return; + } + } + + nsRefPtr task = new ReadFileTask(mInputStream, + mRemoteMaxPacketLength); + rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + BT_WARNING("Cannot dispatch read file task!"); + SendDisconnectRequest(); + } + } else { + BT_WARNING("Unhandled ObexRequestCode"); + } +} + +// Virtual function of class SocketConsumer +void +BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket, + nsAutoPtr& aMessage) +{ + if (mIsServer) { + ServerDataHandler(aMessage); + } else { + ClientDataHandler(aMessage); + } +} + +void +BluetoothOppManager::SendConnectRequest() +{ + if (mConnected) return; + + // Section 3.3.1 "Connect", IrOBEX 1.2 + // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] + // [Headers:var] + uint8_t req[255]; + int index = 7; + + req[3] = 0x10; // version=1.0 + req[4] = 0x00; // flag=0x00 + req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; + req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; + + SendObexData(req, ObexRequestCode::Connect, index); +} + +void +BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName, + int aFileSize) +{ + if (!mConnected) return; + + uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; + + int len = aFileName.Length(); + uint8_t* fileName = new uint8_t[(len + 1) * 2]; + const PRUnichar* fileNamePtr = aFileName.BeginReading(); + + for (int i = 0; i < len; i++) { + fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8); + fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i]; + } + + fileName[len * 2] = 0x00; + fileName[len * 2 + 1] = 0x00; + + int index = 3; + index += AppendHeaderName(&req[index], (char*)fileName, (len + 1) * 2); + index += AppendHeaderLength(&req[index], aFileSize); + + SendObexData(req, ObexRequestCode::Put, index); + + delete [] fileName; + delete [] req; +} + +void +BluetoothOppManager::SendPutRequest(uint8_t* aFileBody, + int aFileBodyLength) +{ + int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize; + + if (!mConnected) return; + if (aFileBodyLength > packetLeftSpace) { + BT_WARNING("Not allowed such a small MaxPacketLength value"); + return; + } + + // Section 3.3.3 "Put", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; + + int index = 3; + index += AppendHeaderBody(&req[index], aFileBody, aFileBodyLength); + + SendObexData(req, ObexRequestCode::Put, index); + delete [] req; + + mSentFileLength += aFileBodyLength; +} + +void +BluetoothOppManager::SendPutFinalRequest() +{ + if (!mConnected) return; + + /** + * Section 2.2.9, "End-of-Body", IrObex 1.2 + * End-of-Body is used to identify the last chunk of the object body. + * For most platforms, a PutFinal packet is sent with an zero length + * End-of-Body header. + */ + + // [opcode:1][length:2] + int index = 3; + uint8_t* req = new uint8_t[mRemoteMaxPacketLength]; + index += AppendHeaderEndOfBody(&req[index]); + + SendObexData(req, ObexRequestCode::PutFinal, index); + delete [] req; + + mWaitingToSendPutFinal = false; +} + +void +BluetoothOppManager::SendDisconnectRequest() +{ + if (!mConnected) return; + + // Section 3.3.2 "Disconnect", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + uint8_t req[255]; + int index = 3; + + SendObexData(req, ObexRequestCode::Disconnect, index); +} + +void +BluetoothOppManager::CheckPutFinal(uint32_t aNumRead) +{ + if (mSentFileLength + aNumRead >= mFileLength) { + mWaitingToSendPutFinal = true; + } +} + +bool +BluetoothOppManager::IsConnected() +{ + return (mConnected && !mSendTransferCompleteFlag); +} + +void +BluetoothOppManager::GetAddress(nsAString& aDeviceAddress) +{ + return mSocket->GetAddress(aDeviceAddress); +} + +void +BluetoothOppManager::ReplyToConnect() +{ + if (mConnected) return; + + // Section 3.3.1 "Connect", IrOBEX 1.2 + // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2] + // [Headers:var] + uint8_t req[255]; + int index = 7; + + req[3] = 0x10; // version=1.0 + req[4] = 0x00; // flag=0x00 + req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8; + req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH; + + SendObexData(req, ObexResponseCode::Success, index); +} + +void +BluetoothOppManager::ReplyToDisconnectOrAbort() +{ + if (!mConnected) return; + + // Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2 + // The format of response packet of "Disconnect" and "Abort" are the same + // [opcode:1][length:2][Headers:var] + uint8_t req[255]; + int index = 3; + + SendObexData(req, ObexResponseCode::Success, index); +} + +void +BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue) +{ + if (!mConnected) return; + + // Section 3.3.2 "Disconnect", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + uint8_t req[255]; + int index = 3; + uint8_t opcode; + + if (aContinue) { + opcode = (aFinal)? ObexResponseCode::Success : + ObexResponseCode::Continue; + } else { + opcode = (aFinal)? ObexResponseCode::Unauthorized : + ObexResponseCode::Unauthorized & (~FINAL_BIT); + } + + SendObexData(req, opcode, index); +} + +void +BluetoothOppManager::ReplyError(uint8_t aError) +{ + if (!mConnected) return; + + // Section 3.2 "Response Format", IrOBEX 1.2 + // [opcode:1][length:2][Headers:var] + uint8_t req[255]; + int index = 3; + + SendObexData(req, aError, index); +} + +void +BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize) +{ + SetObexPacketInfo(aData, aOpcode, aSize); + + if (!mIsServer) { + mLastCommand = aOpcode; + } + + UnixSocketRawData* s = new UnixSocketRawData(aSize); + memcpy(s->mData, aData, s->mSize); + mSocket->SendSocketData(s); +} + +void +BluetoothOppManager::FileTransferComplete() +{ + if (mSendTransferCompleteFlag) { + return; + } + + nsString type, name; + BluetoothValue v; + InfallibleTArray parameters; + type.AssignLiteral("bluetooth-opp-transfer-complete"); + + name.AssignLiteral("address"); + v = mConnectedDeviceAddress; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("success"); + v = mSuccessFlag; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("received"); + v = mIsServer; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileName"); + v = mFileName; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileLength"); + v = mSentFileLength; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("contentType"); + v = mContentType; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + if (!BroadcastSystemMessage(type, parameters)) { + BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-complete]"); + return; + } + + mSendTransferCompleteFlag = true; +} + +void +BluetoothOppManager::StartFileTransfer() +{ + nsString type, name; + BluetoothValue v; + InfallibleTArray parameters; + type.AssignLiteral("bluetooth-opp-transfer-start"); + + name.AssignLiteral("address"); + v = mConnectedDeviceAddress; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("received"); + v = mIsServer; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileName"); + v = mFileName; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileLength"); + v = mFileLength; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("contentType"); + v = mContentType; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + if (!BroadcastSystemMessage(type, parameters)) { + BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-start]"); + return; + } + + mSendTransferCompleteFlag = false; +} + +void +BluetoothOppManager::UpdateProgress() +{ + nsString type, name; + BluetoothValue v; + InfallibleTArray parameters; + type.AssignLiteral("bluetooth-opp-update-progress"); + + name.AssignLiteral("address"); + v = mConnectedDeviceAddress; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("received"); + v = mIsServer; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("processedLength"); + v = mSentFileLength; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileLength"); + v = mFileLength; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + if (!BroadcastSystemMessage(type, parameters)) { + BT_WARNING("Failed to broadcast [bluetooth-opp-update-progress]"); + return; + } +} + +void +BluetoothOppManager::ReceivingFileConfirmation() +{ + nsString type, name; + BluetoothValue v; + InfallibleTArray parameters; + type.AssignLiteral("bluetooth-opp-receiving-file-confirmation"); + + name.AssignLiteral("address"); + v = mConnectedDeviceAddress; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileName"); + v = mFileName; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("fileLength"); + v = mFileLength; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + name.AssignLiteral("contentType"); + v = mContentType; + parameters.AppendElement(BluetoothNamedValue(name, v)); + + if (!BroadcastSystemMessage(type, parameters)) { + BT_WARNING("Failed to send [bluetooth-opp-receiving-file-confirmation]"); + return; + } +} + +void +BluetoothOppManager::NotifyAboutFileChange() +{ + NS_NAMED_LITERAL_STRING(data, "modified"); + + nsCOMPtr obs = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE_VOID(obs); + + obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get()); +} + +void +BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket) +{ + BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client"); + MOZ_ASSERT(aSocket); + + /** + * If the created connection is an inbound connection, close another server + * socket because currently only one file-transfer session is allowed. After + * that, we need to make sure that both server socket would be nulled out. + * As for outbound connections, we just notify the controller that it's done. + */ + if (aSocket == mRfcommSocket) { + MOZ_ASSERT(!mSocket); + mRfcommSocket.swap(mSocket); + + mL2capSocket->Disconnect(); + mL2capSocket = nullptr; + } else if (aSocket == mL2capSocket) { + MOZ_ASSERT(!mSocket); + mL2capSocket.swap(mSocket); + + mRfcommSocket->Disconnect(); + mRfcommSocket = nullptr; + } + + // Cache device address since we can't get socket address when a remote + // device disconnect with us. + mSocket->GetAddress(mConnectedDeviceAddress); + + // Start sending file if we connect as a client + if (!mIsServer) { + StartSendingNextFile(); + } +} + +void +BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket) +{ + BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client"); + + mRfcommSocket = nullptr; + mL2capSocket = nullptr; + mSocket = nullptr; + + if (!mIsServer) { + // Inform gaia of remaining blobs' sending failure + DiscardBlobsToSend(); + } + + // Listen as a server if there's no more batch to process + if (!ProcessNextBatch()) { + Listen(); + } +} + +void +BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) +{ + BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client"); + MOZ_ASSERT(aSocket); + + if (aSocket != mSocket) { + // Do nothing when a listening server socket is closed. + return; + } + + /** + * It is valid for a bluetooth device which is transfering file via OPP + * closing socket without sending OBEX disconnect request first. So we + * delete the broken file when we failed to receive a file from the remote, + * and notify the transfer has been completed (but failed). We also call + * AfterOppDisconnected here to ensure all variables will be cleaned. + */ + if (!mSuccessFlag) { + if (mIsServer) { + DeleteReceivedFile(); + } + + FileTransferComplete(); + if (!mIsServer) { + // Inform gaia of remaining blobs' sending failure + DiscardBlobsToSend(); + } + } + + AfterOppDisconnected(); + mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); + mSuccessFlag = false; + + mSocket = nullptr; + // Listen as a server if there's no more batch to process + if (!ProcessNextBatch()) { + Listen(); + } +} + +void +BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress, + const nsAString& aServiceUuid, + int aChannel) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aDeviceAddress.IsEmpty()); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE_VOID(bs); + + if (aChannel < 0) { + if (mNeedsUpdatingSdpRecords) { + mNeedsUpdatingSdpRecords = false; + bs->UpdateSdpRecords(aDeviceAddress, this); + } else { + OnSocketConnectError(mSocket); + } + + return; + } + + if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) { + OnSocketConnectError(mSocket); + } +} + +void +BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aDeviceAddress.IsEmpty()); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE_VOID(bs); + + nsString uuid; + BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid); + + if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) { + OnSocketConnectError(mSocket); + } +} + +NS_IMPL_ISUPPORTS1(BluetoothOppManager, nsIObserver) + +bool +BluetoothOppManager::AcquireSdcardMountLock() +{ + nsCOMPtr volumeSrv = + do_GetService(NS_VOLUMESERVICE_CONTRACTID); + NS_ENSURE_TRUE(volumeSrv, false); + nsresult rv; + rv = volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"), + getter_AddRefs(mMountLock)); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +void +BluetoothOppManager::Connect(const nsAString& aDeviceAddress, + BluetoothProfileController* aController) +{ + MOZ_ASSERT(false); +} + +void +BluetoothOppManager::Disconnect(BluetoothProfileController* aController) +{ + MOZ_ASSERT(false); +} + +void +BluetoothOppManager::OnConnect(const nsAString& aErrorStr) +{ + MOZ_ASSERT(false); +} + +void +BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr) +{ + MOZ_ASSERT(false); +} diff --git a/dom/bluetooth/bluez/BluetoothOppManager.h b/dom/bluetooth/bluez/BluetoothOppManager.h new file mode 100644 index 000000000000..062032ac69d6 --- /dev/null +++ b/dom/bluetooth/bluez/BluetoothOppManager.h @@ -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& 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 mBodySegment; + nsAutoArrayPtr mReceivedDataBuffer; + + int mCurrentBlobIndex; + nsCOMPtr mBlob; + nsTArray mBatches; + + /** + * A seperate member thread is required because our read calls can block + * execution, which is not allowed to happen on the IOThread. + */ + nsCOMPtr mReadFileThread; + nsCOMPtr mOutputStream; + nsCOMPtr mInputStream; + nsCOMPtr mMountLock; + nsRefPtr 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 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 mRfcommSocket; + nsRefPtr mL2capSocket; +}; + +END_BLUETOOTH_NAMESPACE + +#endif diff --git a/dom/bluetooth/bluez/BluetoothSocket.cpp b/dom/bluetooth/bluez/BluetoothSocket.cpp new file mode 100644 index 000000000000..7cd43147afb9 --- /dev/null +++ b/dom/bluetooth/bluez/BluetoothSocket.cpp @@ -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 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 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& 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); +} + diff --git a/dom/bluetooth/bluez/BluetoothSocket.h b/dom/bluetooth/bluez/BluetoothSocket.h new file mode 100644 index 000000000000..8ee1d86b1495 --- /dev/null +++ b/dom/bluetooth/bluez/BluetoothSocket.h @@ -0,0 +1,52 @@ +/* -*- 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 BluetoothSocket : public mozilla::ipc::UnixSocketConsumer +{ +public: + BluetoothSocket(BluetoothSocketObserver* aObserver, + BluetoothSocketType aType, + bool aAuth, + bool aEncrypt); + + bool Connect(const nsACString& aDeviceAddress, int aChannel); + bool Listen(int aChannel); + inline void Disconnect() + { + CloseSocket(); + } + + virtual void OnConnectSuccess() MOZ_OVERRIDE; + virtual void OnConnectError() MOZ_OVERRIDE; + virtual void OnDisconnect() MOZ_OVERRIDE; + virtual void ReceiveSocketData( + nsAutoPtr& aMessage) MOZ_OVERRIDE; + + inline void GetAddress(nsAString& aDeviceAddress) + { + GetSocketAddr(aDeviceAddress); + } + +private: + BluetoothSocketObserver* mObserver; + BluetoothSocketType mType; + bool mAuth; + bool mEncrypt; +}; + +END_BLUETOOTH_NAMESPACE + +#endif diff --git a/dom/bluetooth/BluetoothUnixSocketConnector.cpp b/dom/bluetooth/bluez/BluetoothUnixSocketConnector.cpp similarity index 100% rename from dom/bluetooth/BluetoothUnixSocketConnector.cpp rename to dom/bluetooth/bluez/BluetoothUnixSocketConnector.cpp diff --git a/dom/bluetooth/BluetoothUnixSocketConnector.h b/dom/bluetooth/bluez/BluetoothUnixSocketConnector.h similarity index 100% rename from dom/bluetooth/BluetoothUnixSocketConnector.h rename to dom/bluetooth/bluez/BluetoothUnixSocketConnector.h diff --git a/dom/bluetooth/moz.build b/dom/bluetooth/moz.build index 8a36a593d353..55b49bafa2bf 100644 --- a/dom/bluetooth/moz.build +++ b/dom/bluetooth/moz.build @@ -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 += [ From 561bfba8edad8b520a93920369866f489600b6ba Mon Sep 17 00:00:00 2001 From: Ben Tian Date: Mon, 2 Dec 2013 10:39:58 +0800 Subject: [PATCH 11/67] Bug 915533 - Patch 2/2: [bludroid OPP] BluetoothSocket, r=echou --- .../bluedroid/BluetoothOppManager.cpp | 179 ++--- dom/bluetooth/bluedroid/BluetoothOppManager.h | 8 +- dom/bluetooth/bluedroid/BluetoothSocket.cpp | 676 +++++++++++++++++- dom/bluetooth/bluedroid/BluetoothSocket.h | 43 +- .../gonk/BluetoothServiceBluedroid.cpp | 54 +- .../bluez/linux/BluetoothDBusService.cpp | 14 +- 6 files changed, 813 insertions(+), 161 deletions(-) diff --git a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp index 9d667530cf5c..05735a1a1315 100644 --- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp @@ -158,11 +158,7 @@ public: void Run() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); - - if (mSocket->GetConnectionStatus() == - SocketConnectionStatus::SOCKET_CONNECTED) { - mSocket->Disconnect(); - } + mSocket->CloseDroidSocket(); } private: @@ -189,7 +185,7 @@ BluetoothOppManager::BluetoothOppManager() : mConnected(false) , mWaitingToSendPutFinal(false) , mCurrentBlobIndex(-1) { - mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); + mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); } BluetoothOppManager::~BluetoothOppManager() @@ -211,7 +207,14 @@ BluetoothOppManager::Init() return false; } - Listen(); + /** + * We don't start listening here as BluetoothServiceBluedroid calls Listen() + * immediately when BT stops. + * + * If we start listening here, the listening fails when device boots up since + * Listen() is called again and restarts server socket. The restart causes + * absence of read events when device boots up. + */ return true; } @@ -244,14 +247,9 @@ BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress) MOZ_ASSERT(NS_IsMainThread()); // Stop listening because currently we only support one connection at a time. - if (mRfcommSocket) { - mRfcommSocket->Disconnect(); - mRfcommSocket = nullptr; - } - - if (mL2capSocket) { - mL2capSocket->Disconnect(); - mL2capSocket = nullptr; + if (mServerSocket) { + mServerSocket->Disconnect(); + mServerSocket = nullptr; } mIsServer = false; @@ -262,18 +260,9 @@ BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress) return; } - mNeedsUpdatingSdpRecords = true; - - nsString uuid; - BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid); - - if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) { - OnSocketConnectError(mSocket); - return; - } - mSocket = new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); + mSocket->Connect(aDeviceAddress, -1); } void @@ -286,13 +275,9 @@ BluetoothOppManager::HandleShutdown() mSocket->Disconnect(); mSocket = nullptr; } - if (mRfcommSocket) { - mRfcommSocket->Disconnect(); - mRfcommSocket = nullptr; - } - if (mL2capSocket) { - mL2capSocket->Disconnect(); - mL2capSocket = nullptr; + if (mServerSocket) { + mServerSocket->Disconnect(); + mServerSocket = nullptr; } sBluetoothOppManager = nullptr; } @@ -307,28 +292,23 @@ BluetoothOppManager::Listen() return false; } - if (!mRfcommSocket) { - mRfcommSocket = - new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); - - if (!mRfcommSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) { - BT_WARNING("[OPP] Can't listen on RFCOMM socket!"); - mRfcommSocket = nullptr; - return false; - } + /** + * Restart server socket since its underlying fd becomes invalid when + * BT stops; otherwise no more read events would be received even if + * BT restarts. + */ + if (mServerSocket) { + mServerSocket->Disconnect(); + mServerSocket = nullptr; } - if (!mL2capSocket) { - mL2capSocket = - new BluetoothSocket(this, BluetoothSocketType::EL2CAP, true, true); + mServerSocket = + new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true); - if (!mL2capSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH_L2CAP)) { - BT_WARNING("[OPP] Can't listen on L2CAP socket!"); - mRfcommSocket->Disconnect(); - mRfcommSocket = nullptr; - mL2capSocket = nullptr; - return false; - } + if (!mServerSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) { + BT_WARNING("[OPP] Can't listen on RFCOMM socket!"); + mServerSocket = nullptr; + return false; } mIsServer = true; @@ -1250,7 +1230,7 @@ BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize) UnixSocketRawData* s = new UnixSocketRawData(aSize); memcpy(s->mData, aData, s->mSize); - mSocket->SendSocketData(s); + mSocket->SendDroidSocketData(s); } void @@ -1266,7 +1246,7 @@ BluetoothOppManager::FileTransferComplete() type.AssignLiteral("bluetooth-opp-transfer-complete"); name.AssignLiteral("address"); - v = mConnectedDeviceAddress; + v = mDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("success"); @@ -1306,7 +1286,7 @@ BluetoothOppManager::StartFileTransfer() type.AssignLiteral("bluetooth-opp-transfer-start"); name.AssignLiteral("address"); - v = mConnectedDeviceAddress; + v = mDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("received"); @@ -1342,7 +1322,7 @@ BluetoothOppManager::UpdateProgress() type.AssignLiteral("bluetooth-opp-update-progress"); name.AssignLiteral("address"); - v = mConnectedDeviceAddress; + v = mDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("received"); @@ -1372,7 +1352,7 @@ BluetoothOppManager::ReceivingFileConfirmation() type.AssignLiteral("bluetooth-opp-receiving-file-confirmation"); name.AssignLiteral("address"); - v = mConnectedDeviceAddress; + v = mDeviceAddress; parameters.AppendElement(BluetoothNamedValue(name, v)); name.AssignLiteral("fileName"); @@ -1412,28 +1392,19 @@ BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_ASSERT(aSocket); /** - * If the created connection is an inbound connection, close another server - * socket because currently only one file-transfer session is allowed. After - * that, we need to make sure that both server socket would be nulled out. + * If the created connection is an inbound connection, close server socket + * because currently only one file-transfer session is allowed. After that, + * we need to make sure that server socket would be nulled out. * As for outbound connections, we just notify the controller that it's done. */ - if (aSocket == mRfcommSocket) { + if (aSocket == mServerSocket) { MOZ_ASSERT(!mSocket); - mRfcommSocket.swap(mSocket); - - mL2capSocket->Disconnect(); - mL2capSocket = nullptr; - } else if (aSocket == mL2capSocket) { - MOZ_ASSERT(!mSocket); - mL2capSocket.swap(mSocket); - - mRfcommSocket->Disconnect(); - mRfcommSocket = nullptr; + mServerSocket.swap(mSocket); } // Cache device address since we can't get socket address when a remote // device disconnect with us. - mSocket->GetAddress(mConnectedDeviceAddress); + mSocket->GetAddress(mDeviceAddress); // Start sending file if we connect as a client if (!mIsServer) { @@ -1446,8 +1417,7 @@ BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket) { BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client"); - mRfcommSocket = nullptr; - mL2capSocket = nullptr; + mServerSocket = nullptr; mSocket = nullptr; if (!mIsServer) { @@ -1464,13 +1434,12 @@ BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket) void BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) { - BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client"); MOZ_ASSERT(aSocket); - if (aSocket != mSocket) { // Do nothing when a listening server socket is closed. return; } + BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer) ? "client" : "server"); /** * It is valid for a bluetooth device which is transfering file via OPP @@ -1492,7 +1461,7 @@ BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) } AfterOppDisconnected(); - mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); + mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE); mSuccessFlag = false; mSocket = nullptr; @@ -1502,50 +1471,6 @@ BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket) } } -void -BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress, - const nsAString& aServiceUuid, - int aChannel) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!aDeviceAddress.IsEmpty()); - - BluetoothService* bs = BluetoothService::Get(); - NS_ENSURE_TRUE_VOID(bs); - - if (aChannel < 0) { - if (mNeedsUpdatingSdpRecords) { - mNeedsUpdatingSdpRecords = false; - bs->UpdateSdpRecords(aDeviceAddress, this); - } else { - OnSocketConnectError(mSocket); - } - - return; - } - - if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) { - OnSocketConnectError(mSocket); - } -} - -void -BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!aDeviceAddress.IsEmpty()); - - BluetoothService* bs = BluetoothService::Get(); - NS_ENSURE_TRUE_VOID(bs); - - nsString uuid; - BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid); - - if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) { - OnSocketConnectError(mSocket); - } -} - NS_IMPL_ISUPPORTS1(BluetoothOppManager, nsIObserver) bool @@ -1561,6 +1486,20 @@ BluetoothOppManager::AcquireSdcardMountLock() return true; } +void +BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress, + const nsAString& aServiceUuid, + int aChannel) +{ + MOZ_ASSERT(false); +} + +void +BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress) +{ + MOZ_ASSERT(false); +} + void BluetoothOppManager::Connect(const nsAString& aDeviceAddress, BluetoothProfileController* aController) diff --git a/dom/bluetooth/bluedroid/BluetoothOppManager.h b/dom/bluetooth/bluedroid/BluetoothOppManager.h index 062032ac69d6..0642c2457cee 100644 --- a/dom/bluetooth/bluedroid/BluetoothOppManager.h +++ b/dom/bluetooth/bluedroid/BluetoothOppManager.h @@ -122,7 +122,7 @@ private: * Set when OBEX session is established. */ bool mConnected; - nsString mConnectedDeviceAddress; + nsString mDeviceAddress; /** * Remote information @@ -212,15 +212,13 @@ private: // 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). + // mSocket is non-null, mServerSocket must be null (and vice versa). nsRefPtr 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 mRfcommSocket; - nsRefPtr mL2capSocket; + nsRefPtr mServerSocket; }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/bluedroid/BluetoothSocket.cpp b/dom/bluetooth/bluedroid/BluetoothSocket.cpp index 7cd43147afb9..ae6b9d8384e1 100644 --- a/dom/bluetooth/bluedroid/BluetoothSocket.cpp +++ b/dom/bluetooth/bluedroid/BluetoothSocket.cpp @@ -6,60 +6,692 @@ #include "BluetoothSocket.h" +#include +#include +#include + +#include "base/message_loop.h" +#include "BluetoothServiceBluedroid.h" #include "BluetoothSocketObserver.h" -#include "BluetoothUnixSocketConnector.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 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 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 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 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 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 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 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 t = new RequestClosingSocketTask(this); + NS_DispatchToMainThread(t); + return; + } + + incoming->mSize = ret; + nsRefPtr 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) - , mType(aType) + , 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::Connect(const nsACString& aDeviceAddress, int aChannel) +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); - nsAutoPtr c( - new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt)); + bt_bdaddr_t remoteBdAddress; + StringToBdAddressType(aDeviceAddress, &remoteBdAddress); - 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; - } + // 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); - return true; + mIsServer = false; + return CreateDroidSocket(fd); } bool BluetoothSocket::Listen(int aChannel) { MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_TRUE(sBluetoothSocketInterface, false); - nsAutoPtr c( - new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt)); + // 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); - if (!ListenSocket(c.forget())) { - nsAutoString addr; - GetAddress(addr); - BT_LOGD("%s failed. Current connected device address: %s", - __FUNCTION__, NS_ConvertUTF16toUTF8(addr).get()); + 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& 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; } @@ -67,6 +699,10 @@ BluetoothSocket::Listen(int aChannel) void BluetoothSocket::ReceiveSocketData(nsAutoPtr& aMessage) { + if (ReceiveSocketInfo(aMessage)) { + return; + } + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mObserver); mObserver->ReceiveSocketData(this, aMessage); diff --git a/dom/bluetooth/bluedroid/BluetoothSocket.h b/dom/bluetooth/bluedroid/BluetoothSocket.h index 8ee1d86b1495..79b44e43b6de 100644 --- a/dom/bluetooth/bluedroid/BluetoothSocket.h +++ b/dom/bluetooth/bluedroid/BluetoothSocket.h @@ -13,6 +13,7 @@ BEGIN_BLUETOOTH_NAMESPACE class BluetoothSocketObserver; +class DroidSocketImpl; class BluetoothSocket : public mozilla::ipc::UnixSocketConsumer { @@ -22,11 +23,35 @@ public: bool aAuth, bool aEncrypt); - bool Connect(const nsACString& aDeviceAddress, int aChannel); + /** + * 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() { - CloseSocket(); + CloseDroidSocket(); } virtual void OnConnectSuccess() MOZ_OVERRIDE; @@ -37,14 +62,24 @@ public: inline void GetAddress(nsAString& aDeviceAddress) { - GetSocketAddr(aDeviceAddress); + aDeviceAddress = mDeviceAddress; } + void CloseDroidSocket(); + bool IsWaitingForClientFd(); + bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData); + private: BluetoothSocketObserver* mObserver; - BluetoothSocketType mType; + DroidSocketImpl* mImpl; + nsString mDeviceAddress; bool mAuth; bool mEncrypt; + bool mIsServer; + int mReceivedSocketInfoLength; + + bool CreateDroidSocket(int aFd); + bool ReceiveSocketInfo(nsAutoPtr& aMessage); }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp index 3bdd70cdcef7..1dede2b2fdcc 100644 --- a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp @@ -20,8 +20,9 @@ #include -#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 diff --git a/dom/bluetooth/bluez/linux/BluetoothDBusService.cpp b/dom/bluetooth/bluez/linux/BluetoothDBusService.cpp index f1cbd7a1f337..37cefe3a6a0d 100644 --- a/dom/bluetooth/bluez/linux/BluetoothDBusService.cpp +++ b/dom/bluetooth/bluez/linux/BluetoothDBusService.cpp @@ -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"); } From aa9fa6a12baefa5636bc7e1d8f710cab17086d94 Mon Sep 17 00:00:00 2001 From: Ben Tian Date: Mon, 2 Dec 2013 16:21:53 +0800 Subject: [PATCH 12/67] bug 915533 - opp-clobber, r=echou --- CLOBBER | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLOBBER b/CLOBBER index 66b1893ef8dd..0495c0788a13 100644 --- a/CLOBBER +++ b/CLOBBER @@ -18,4 +18,4 @@ # Modifying this file will now automatically clobber the buildbot machines \o/ # -Another Windows WebIDL clobber needed due to bug 928195 +Bug 915533 - need clobber as source files are relocated From 4b34aee4683953839b39bb1c0e25b4888aa325f5 Mon Sep 17 00:00:00 2001 From: Jamin Liu Date: Mon, 2 Dec 2013 16:38:51 +0800 Subject: [PATCH 13/67] Bug 943753 - Fix the incorrect condition for synchronous connection to support HSP. r=echou We don't need to check SLC status for HSP. --- dom/bluetooth/bluez/BluetoothHfpManager.cpp | 8 +++++--- dom/bluetooth/bluez/BluetoothHfpManager.h | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dom/bluetooth/bluez/BluetoothHfpManager.cpp b/dom/bluetooth/bluez/BluetoothHfpManager.cpp index 41475e292509..814b562c4b3b 100644 --- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp @@ -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; diff --git a/dom/bluetooth/bluez/BluetoothHfpManager.h b/dom/bluetooth/bluez/BluetoothHfpManager.h index 2e01783861a8..91f78c6cd99e 100644 --- a/dom/bluetooth/bluez/BluetoothHfpManager.h +++ b/dom/bluetooth/bluez/BluetoothHfpManager.h @@ -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; From 48b5a600e5d28eda7230abe2b5b8914b814cfb44 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 01:50:23 -0800 Subject: [PATCH 14/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/44fa8d354378 Author: Rudy Lu Desc: Merge pull request #14249 from RudyLu/keyboard/Bug942065-make_auto_suggestion_output_full_word Bug 942065 - [b2g] auto suggestion uses truncated/shortend words. r=timdream ======== https://hg.mozilla.org/integration/gaia-central/rev/41b36feff7a7 Author: Rudy Lu Desc: Bug 942065 - [b2g] auto suggestion uses truncated/shortend words - make latin IME output the full word for word suggestion --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index bdefa19cbdaa..b9d0bb119efb 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "a8674008468e815eda97b9501862f721e4ba4df7", + "revision": "44fa8d35437871899bc18fe46e3dbe20569171a0", "repo_path": "/integration/gaia-central" } From 357424523e49847aa95d762c102b450cb6733b1e Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 02:15:24 -0800 Subject: [PATCH 15/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/1e1ffec09757 Author: gasolin Desc: Merge pull request #14253 from MBRSL/bug-943784-radio Bug 943784 - [UITest][HW] remove 'var navigator = window.navigator' in r..., r=gasolin ======== https://hg.mozilla.org/integration/gaia-central/rev/1aa27a60bb53 Author: Tom Jao Desc: Bug 943784 - [UITest][HW] remove 'var navigator = window.navigator' in radio test --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index b9d0bb119efb..a1b1e76f4938 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "44fa8d35437871899bc18fe46e3dbe20569171a0", + "revision": "1e1ffec09757bf0090f5ab2c29011ae83fb3cdb8", "repo_path": "/integration/gaia-central" } From 81191ac3a57337e5bc30823b364540616fa2a909 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 03:05:23 -0800 Subject: [PATCH 16/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/890550ceed75 Author: Evyatar Amitay Desc: Merge pull request #14252 from EverythingMe/944026-installed-size-shadow [Bug 944026] Fix icon size and shadow [r=amirn] ======== https://hg.mozilla.org/integration/gaia-central/rev/ed72f6607e67 Author: Evyatar Amitay Desc: [Bug 944026] Fix icon size and shadow [r=amirn] --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index a1b1e76f4938..c1244afd5dca 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "1e1ffec09757bf0090f5ab2c29011ae83fb3cdb8", + "revision": "890550ceed753886df7b6ac2138e88c2d38f17d8", "repo_path": "/integration/gaia-central" } From 16b3d8837038b27fcd7e334d5f94612107fbf194 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 03:30:23 -0800 Subject: [PATCH 17/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/8d1fddd00cca Author: Joan Leon Desc: Merge pull request #14257 from nucliweb/BB-validate BB validate ======== https://hg.mozilla.org/integration/gaia-central/rev/d8a9334d039d Author: Joan Leon Desc: [BB] Fix to pass W3C Validator Fix font-size on shared/style_unstable/lists.css on [data-type="list"] aside.icon --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index c1244afd5dca..569d895e46d1 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "890550ceed753886df7b6ac2138e88c2d38f17d8", + "revision": "8d1fddd00ccad26082e7e8eb3cf60c0ef43bd070", "repo_path": "/integration/gaia-central" } From 0b3d11055f93f99972b7b567e462b4291de5bca1 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 03:55:23 -0800 Subject: [PATCH 18/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/30c31f374af2 Author: Zac Desc: Merge pull request #14027 from sashakruglov/music_application migrate music app tests to app object ======== https://hg.mozilla.org/integration/gaia-central/rev/72c1117d24a7 Author: Sasha Kruglov Desc: migrate music app tests to app object --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 569d895e46d1..56071e54e2cb 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "8d1fddd00ccad26082e7e8eb3cf60c0ef43bd070", + "revision": "30c31f374af27589c80d36e129ba2aa1fa5978f7", "repo_path": "/integration/gaia-central" } From dba9714245deecb99f772f67fabd1364c80bde52 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 06:50:23 -0800 Subject: [PATCH 19/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/e6c9bb061b29 Author: Florin Strugariu Desc: Merge pull request #14183 from zacc/bug_942107_clock Bug 942107 - Clock app, be smart about our locators! ======== https://hg.mozilla.org/integration/gaia-central/rev/e5de911fc7e3 Author: Zac Campbell Desc: Bug 942107 - Clock app, be smart about our locators! --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 56071e54e2cb..85ba93bc9694 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "30c31f374af27589c80d36e129ba2aa1fa5978f7", + "revision": "e6c9bb061b29a0315234d76537370d1686c0060e", "repo_path": "/integration/gaia-central" } From 144fec8a930c17e4d85af0fb69a41dcd6c4cc540 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 06:50:46 -0800 Subject: [PATCH 20/67] Bumping gaia.json for 3 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/1ae8969c7d8e Author: Jan Jongboom Desc: Merge pull request #11386 from ltedone/Bug_902120-Add_and_update_wordlist_files Bug 902120 - Add and update wordlist files. r=janjongboom ======== https://hg.mozilla.org/integration/gaia-central/rev/702e28a368f0 Author: Luigi Tedone Desc: Added and updated wordlist files and dictionary files ======== https://hg.mozilla.org/integration/gaia-central/rev/09b4978ae9dc Author: Zac Campbell Desc: Bug 945227 - tap_back_from_import_contacts needs a stronger wait r=me --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 85ba93bc9694..1c7381765d2b 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "e6c9bb061b29a0315234d76537370d1686c0060e", + "revision": "1ae8969c7d8e4819f9926e1da5d7c1aeb8c61eb6", "repo_path": "/integration/gaia-central" } From 196a737ae92bd8b907a294936beeb32deb6aec20 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 06:56:23 -0800 Subject: [PATCH 21/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/e82e1705b79c Author: Jan Jongboom Desc: Merge pull request #14262 from comoyo/935904 Bug 935904 - Un-intermittent test in keyboard_manager. r=janjongboom ======== https://hg.mozilla.org/integration/gaia-central/rev/4385b4636a48 Author: Jan Jongboom Desc: Bug 935904 - Un-intermittent test in keyboard_manager --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 1c7381765d2b..daab1a97538d 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "1ae8969c7d8e4819f9926e1da5d7c1aeb8c61eb6", + "revision": "e82e1705b79c349910bf66a015a681fdd0ec3500", "repo_path": "/integration/gaia-central" } From 80a43b37849f1cf1b258fbfb6d53199f6b0fd426 Mon Sep 17 00:00:00 2001 From: "Antonio M. Amaya" Date: Tue, 19 Nov 2013 20:41:54 +0100 Subject: [PATCH 22/67] Bug 926219 - Relax the signature validation for locally installed apps. r=bsmith, r=fabrice --- dom/apps/src/Webapps.jsm | 53 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 0d8f50511c7f..3f50b855e222 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -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); From 43648319248cdd8264289f0a327fefc39b3335a2 Mon Sep 17 00:00:00 2001 From: Steven Lee Date: Mon, 2 Dec 2013 10:21:07 -0500 Subject: [PATCH 23/67] Bug 926746 - Part 1: Merge fakeperm into b2g. r=mwu --- widget/gonk/GonkPermission.cpp | 126 +++++++++++++++++++++++++++++++++ widget/gonk/GonkPermission.h | 84 ++++++++++++++++++++++ widget/gonk/moz.build | 2 + widget/gonk/nsAppShell.cpp | 2 + 4 files changed, 214 insertions(+) create mode 100644 widget/gonk/GonkPermission.cpp create mode 100644 widget/gonk/GonkPermission.h diff --git a/widget/gonk/GonkPermission.cpp b/widget/gonk/GonkPermission.cpp new file mode 100644 index 000000000000..faa0f15b1a66 --- /dev/null +++ b/widget/gonk/GonkPermission.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * 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. + */ + +#include +#include +#include +#include +#include +#include "GonkPermission.h" + +#undef LOG +#include +#define ALOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "gonkperm" , ## args) + +using namespace android; +using namespace mozilla; + +bool +GonkPermissionService::checkPermission(const String16& permission, int32_t pid, + int32_t uid) +{ + if (0 == uid) + return true; + + // Camera/audio record permissions are only for apps with the + // "camera" permission. These apps are also the only apps granted + // the AID_SDCARD_RW supplemental group (bug 785592) + + if (uid < AID_APP) { + ALOGE("%s for pid=%d,uid=%d denied: not an app", + String8(permission).string(), pid, uid); + return false; + } + + String8 perm8(permission); + + if (perm8 != "android.permission.CAMERA" && + perm8 != "android.permission.RECORD_AUDIO") { + ALOGE("%s for pid=%d,uid=%d denied: unsupported permission", + String8(permission).string(), pid, uid); + return false; + } + + // Users granted the permission through a prompt dialog. + // Before permission managment of gUM is done, app cannot remember the + // permission. + PermissionGrant permGrant(perm8.string(), pid); + if (nsTArray::NoIndex != mGrantArray.IndexOf(permGrant)) { + mGrantArray.RemoveElement(permGrant); + return true; + } + + char filename[32]; + snprintf(filename, sizeof(filename), "/proc/%d/status", pid); + FILE *f = fopen(filename, "r"); + if (!f) { + ALOGE("%s for pid=%d,uid=%d denied: unable to open %s", + String8(permission).string(), pid, uid, filename); + return false; + } + + char line[80]; + while (fgets(line, sizeof(line), f)) { + char *save; + char *name = strtok_r(line, "\t", &save); + if (!name) + continue; + + if (strcmp(name, "Groups:")) + continue; + char *group; + while ((group = strtok_r(NULL, " \n", &save))) { + #define _STR(x) #x + #define STR(x) _STR(x) + if (!strcmp(group, STR(AID_SDCARD_RW))) { + fclose(f); + return true; + } + } + break; + } + fclose(f); + + ALOGE("%s for pid=%d,uid=%d denied: missing group", + String8(permission).string(), pid, uid); + return false; +} + +static GonkPermissionService* gGonkPermissionService = NULL; + +/* static */ +void +GonkPermissionService::instantiate() +{ + defaultServiceManager()->addService(String16(getServiceName()), + GetInstance()); +} + +/* static */ +GonkPermissionService* +GonkPermissionService::GetInstance() +{ + if (!gGonkPermissionService) { + gGonkPermissionService = new GonkPermissionService(); + } + return gGonkPermissionService; +} + +void +GonkPermissionService::addGrantInfo(const char* permission, int32_t pid) +{ + mGrantArray.AppendElement(PermissionGrant(permission, pid)); +} diff --git a/widget/gonk/GonkPermission.h b/widget/gonk/GonkPermission.h new file mode 100644 index 000000000000..5e40b2a2779a --- /dev/null +++ b/widget/gonk/GonkPermission.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * 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. + */ +#ifndef GONKPERMISSION_H +#define GONKPERMISSION_H + +#include +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +class PermissionGrant +{ +public: + PermissionGrant(const char* perm, int32_t p) : mPid(p) + { + mPermission.Assign(perm); + } + + PermissionGrant(const nsACString& permission, int32_t pid) : mPid(pid), + mPermission(permission) + { + } + + bool operator==(const PermissionGrant& other) const + { + return (mPid == other.pid() && mPermission.Equals(other.permission())); + } + + int32_t pid() const + { + return mPid; + } + + const nsACString& permission() const + { + return mPermission; + } + +private: + int32_t mPid; + nsCString mPermission; +}; + +class PermissionGrant; + +class GonkPermissionService : + public android::BinderService, + public android::BnPermissionController +{ +public: + virtual ~GonkPermissionService() {} + static GonkPermissionService* GetInstance(); + static const char *getServiceName() { + return "permission"; + } + + static void instantiate(); + + virtual android::status_t dump(int fd, const android::Vector& args) { + return android::NO_ERROR; + } + virtual bool checkPermission(const android::String16& permission, int32_t pid, + int32_t uid); + + void addGrantInfo(const char* permission, int32_t pid); +private: + GonkPermissionService(): android::BnPermissionController() {} + nsTArray mGrantArray; +}; +} // namespace mozilla +#endif // GONKPERMISSION_H diff --git a/widget/gonk/moz.build b/widget/gonk/moz.build index 17bfac5c551e..22948131ea22 100644 --- a/widget/gonk/moz.build +++ b/widget/gonk/moz.build @@ -15,6 +15,7 @@ # limitations under the License. EXPORTS += [ + 'GonkPermission.h', 'OrientationObserver.h', ] @@ -46,6 +47,7 @@ SOURCES += [ 'Framebuffer.cpp', 'GfxInfo.cpp', 'GonkMemoryPressureMonitoring.cpp', + 'GonkPermission.cpp', 'HwcComposer2D.cpp', 'HwcUtils.cpp', 'nsAppShell.cpp', diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp index d4fe27f44ee8..1466d2e96194 100644 --- a/widget/gonk/nsAppShell.cpp +++ b/widget/gonk/nsAppShell.cpp @@ -29,6 +29,7 @@ #include #include "base/basictypes.h" +#include "GonkPermission.h" #include "nscore.h" #ifdef MOZ_OMX_DECODER #include "MediaResourceManagerService.h" @@ -758,6 +759,7 @@ nsAppShell::Init() #if ANDROID_VERSION >= 18 android::FakeSurfaceComposer::instantiate(); #endif + GonkPermissionService::instantiate(); } nsCOMPtr obsServ = GetObserverService(); From 27232f6777da10b6b70109cc00287620df3f6999 Mon Sep 17 00:00:00 2001 From: Steven Lee Date: Mon, 2 Dec 2013 10:21:51 -0500 Subject: [PATCH 24/67] Bug 926746 - Part 2: nsContentPermissionHelper set grant information to GonkPermission. r=jesup --- .../media/webrtc/MediaEngineWebRTCAudio.cpp | 5 +- dom/base/nsContentPermissionHelper.cpp | 13 ++++ dom/media/MediaManager.cpp | 72 ++++++++----------- dom/media/MediaManager.h | 69 ++++++++++++++++-- 4 files changed, 108 insertions(+), 51 deletions(-) diff --git a/content/media/webrtc/MediaEngineWebRTCAudio.cpp b/content/media/webrtc/MediaEngineWebRTCAudio.cpp index 15125c1803eb..25a2fa4957fb 100644 --- a/content/media/webrtc/MediaEngineWebRTCAudio.cpp +++ b/content/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -284,13 +284,16 @@ MediaEngineWebRTCAudioSource::Init() return; } +#ifndef MOZ_B2G + // Because of the permission mechanism of B2G, we need to skip the status + // check here. bool avail = false; ptrVoEHw->GetRecordingDeviceStatus(avail); ptrVoEHw->Release(); if (!avail) { return; } - +#endif // MOZ_B2G // Set "codec" to PCM, 32kHz on 1 channel webrtc::VoECodec* ptrVoECodec; webrtc::CodecInst codec; diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp index b4b3c87dca5a..4ddfbe51aeb9 100644 --- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -2,6 +2,10 @@ * 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/. */ +#ifdef MOZ_WIDGET_GONK +#include "GonkPermission.h" +#include "mozilla/dom/ContentParent.h" +#endif // MOZ_WIDGET_GONK #include "nsContentPermissionHelper.h" #include "nsIContentPermissionPrompt.h" #include "nsCOMPtr.h" @@ -14,6 +18,7 @@ using mozilla::unused; // using namespace mozilla::dom; +using namespace mozilla; nsContentPermissionRequestProxy::nsContentPermissionRequestProxy() { @@ -130,6 +135,14 @@ nsContentPermissionRequestProxy::Allow() return NS_ERROR_FAILURE; } +#ifdef MOZ_WIDGET_GONK + if (mType.Equals("audio-capture")) { + GonkPermissionService::GetInstance()->addGrantInfo( + "android.permission.RECORD_AUDIO", + static_cast(mParent->Manager())->Manager()->Pid()); + } +#endif + unused << ContentPermissionRequestParent::Send__delete__(mParent, true); mParent = nullptr; return NS_OK; diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 294dc56d9e9c..027f9d9fa946 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -182,49 +182,34 @@ CreateRecordingDeviceEventsSubject(nsPIDOMWindow* aWindow, return props.forget(); } -/** - * Send an error back to content. The error is the form a string. - * Do this only on the main thread. The success callback is also passed here - * so it can be released correctly. - */ -class ErrorCallbackRunnable : public nsRunnable -{ -public: - ErrorCallbackRunnable( - already_AddRefed aSuccess, - already_AddRefed aError, - const nsAString& aErrorMsg, uint64_t aWindowID) - : mSuccess(aSuccess) - , mError(aError) - , mErrorMsg(aErrorMsg) - , mWindowID(aWindowID) - , mManager(MediaManager::GetInstance()) {} - - NS_IMETHOD - Run() - { - // Only run if the window is still active. - NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); - - nsCOMPtr success(mSuccess); - nsCOMPtr error(mError); - - if (!(mManager->IsWindowStillActive(mWindowID))) { - return NS_OK; - } - // This is safe since we're on main-thread, and the windowlist can only - // be invalidated from the main-thread (see OnNavigation) - error->OnError(mErrorMsg); - return NS_OK; +ErrorCallbackRunnable::ErrorCallbackRunnable( + already_AddRefed aSuccess, + already_AddRefed aError, + const nsAString& aErrorMsg, uint64_t aWindowID) + : mSuccess(aSuccess) + , mError(aError) + , mErrorMsg(aErrorMsg) + , mWindowID(aWindowID) + , mManager(MediaManager::GetInstance()) { } -private: - already_AddRefed mSuccess; - already_AddRefed mError; - const nsString mErrorMsg; - uint64_t mWindowID; - nsRefPtr mManager; // get ref to this when creating the runnable -}; +NS_IMETHODIMP +ErrorCallbackRunnable::Run() +{ + // Only run if the window is still active. + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + + nsCOMPtr success(mSuccess); + nsCOMPtr error(mError); + + if (!(mManager->IsWindowStillActive(mWindowID))) { + return NS_OK; + } + // This is safe since we're on main-thread, and the windowlist can only + // be invalidated from the main-thread (see OnNavigation) + error->OnError(mErrorMsg); + return NS_OK; +} /** * Invoke the "onSuccess" callback in content. The callback will take a @@ -634,7 +619,8 @@ public: nsRefPtr runnable( new MediaOperationRunnable(MEDIA_START, mListener, trackunion, tracksAvailableCallback, - mAudioSource, mVideoSource, false, mWindowID)); + mAudioSource, mVideoSource, false, mWindowID, + mError.forget())); mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); #ifdef MOZ_WEBRTC @@ -1782,7 +1768,7 @@ GetUserMediaCallbackMediaStreamListener::Invalidate() runnable = new MediaOperationRunnable(MEDIA_STOP, this, nullptr, nullptr, mAudioSource, mVideoSource, - mFinished, mWindowID); + mFinished, mWindowID, nullptr); mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); } diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 6e00da0079c9..f8c71fee1853 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -212,9 +212,11 @@ class GetUserMediaNotificationEvent: public nsRunnable GetUserMediaNotificationEvent(GetUserMediaStatus aStatus, already_AddRefed aStream, DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback, - bool aIsAudio, bool aIsVideo, uint64_t aWindowID) + bool aIsAudio, bool aIsVideo, uint64_t aWindowID, + already_AddRefed aError) : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback), - mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID) {} + mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID), + mError(aError) {} virtual ~GetUserMediaNotificationEvent() { @@ -230,6 +232,7 @@ class GetUserMediaNotificationEvent: public nsRunnable bool mIsAudio; bool mIsVideo; uint64_t mWindowID; + nsRefPtr mError; }; typedef enum { @@ -237,6 +240,42 @@ typedef enum { MEDIA_STOP } MediaOperation; +class MediaManager; + +/** + * Send an error back to content. The error is the form a string. + * Do this only on the main thread. The success callback is also passed here + * so it can be released correctly. + */ +class ErrorCallbackRunnable : public nsRunnable +{ +public: + ErrorCallbackRunnable( + already_AddRefed aSuccess, + already_AddRefed aError, + const nsAString& aErrorMsg, uint64_t aWindowID); + NS_IMETHOD Run(); +private: + already_AddRefed mSuccess; + already_AddRefed mError; + const nsString mErrorMsg; + uint64_t mWindowID; + nsRefPtr mManager; // get ref to this when creating the runnable +}; + +class ReleaseMediaOperationResource : public nsRunnable +{ +public: + ReleaseMediaOperationResource(already_AddRefed aStream, + DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback): + mStream(aStream), + mOnTracksAvailableCallback(aOnTracksAvailableCallback) {} + NS_IMETHOD Run() MOZ_OVERRIDE {return NS_OK;} +private: + nsRefPtr mStream; + nsAutoPtr mOnTracksAvailableCallback; +}; + // Generic class for running long media operations like Start off the main // thread, and then (because nsDOMMediaStreams aren't threadsafe), // ProxyReleases mStream since it's cycle collected. @@ -251,7 +290,8 @@ public: MediaEngineSource* aAudioSource, MediaEngineSource* aVideoSource, bool aNeedsFinish, - uint64_t aWindowID) + uint64_t aWindowID, + already_AddRefed aError) : mType(aType) , mStream(aStream) , mOnTracksAvailableCallback(aOnTracksAvailableCallback) @@ -260,13 +300,27 @@ public: , mListener(aListener) , mFinish(aNeedsFinish) , mWindowID(aWindowID) - {} + , mError(aError) + {} ~MediaOperationRunnable() { // MediaStreams can be released on any thread. } + nsresult returnAndCallbackError(nsresult rv, const char* errorLog) + { + MM_LOG(("%s , rv=%d", errorLog, rv)); + NS_DispatchToMainThread(new ReleaseMediaOperationResource(mStream.forget(), + mOnTracksAvailableCallback.forget())); + nsString log; + + log.AssignASCII(errorLog, strlen(errorLog)); + NS_DispatchToMainThread(new ErrorCallbackRunnable(nullptr, mError.forget(), + log, mWindowID)); + return NS_OK; + } + NS_IMETHOD Run() MOZ_OVERRIDE { @@ -290,7 +344,7 @@ public: if (NS_SUCCEEDED(rv)) { expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO; } else { - MM_LOG(("Starting audio failed, rv=%d",rv)); + return returnAndCallbackError(rv, "Starting audio failed"); } } if (mVideoSource) { @@ -298,7 +352,7 @@ public: if (NS_SUCCEEDED(rv)) { expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO; } else { - MM_LOG(("Starting video failed, rv=%d",rv)); + return returnAndCallbackError(rv, "Starting video failed"); } } @@ -314,7 +368,7 @@ public: mOnTracksAvailableCallback.forget(), mAudioSource != nullptr, mVideoSource != nullptr, - mWindowID); + mWindowID, mError.forget()); NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); } break; @@ -361,6 +415,7 @@ private: nsRefPtr mListener; // threadsafe bool mFinish; uint64_t mWindowID; + nsRefPtr mError; }; typedef nsTArray > StreamListeners; From 61ac84169bbbb26c78caac7076f08bd4d8ba3a3f Mon Sep 17 00:00:00 2001 From: Sotaro Ikeda Date: Mon, 2 Dec 2013 10:22:33 -0500 Subject: [PATCH 25/67] Bug 929029 - Use nsMainThreadPtrHandle in MediaResource. r=roc, r=doublec --- content/media/MediaResource.cpp | 26 ++++++++++++++------------ content/media/MediaResource.h | 11 ++++++----- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/content/media/MediaResource.cpp b/content/media/MediaResource.cpp index 8e150cb16ffb..c1f15ad657d9 100644 --- a/content/media/MediaResource.cpp +++ b/content/media/MediaResource.cpp @@ -452,7 +452,7 @@ nsresult ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags) { - mChannel = aNew; + mChannel = new nsMainThreadPtrHolder(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 loadGroup = element->GetDocumentLoadGroup(); NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER); - nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), + nsCOMPtr channel; + nsresult rv = NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, loadFlags); + mChannel = new nsMainThreadPtrHolder(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 FileMediaResource::GetCurrentPrincipal() nsCOMPtr 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; } diff --git a/content/media/MediaResource.h b/content/media/MediaResource.h index cdad5e368d30..652e05212287 100644 --- a/content/media/MediaResource.h +++ b/content/media/MediaResource.h @@ -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(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(aChannel)), + mURI(new nsMainThreadPtrHolder(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 mChannel; + nsMainThreadPtrHandle mChannel; // URI in case the stream needs to be re-opened. Access from // main thread only. - nsCOMPtr mURI; + nsMainThreadPtrHandle 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 From 227ca9eaa1b62387d36214b81a841b99df5996d9 Mon Sep 17 00:00:00 2001 From: Sushil Chauhan Date: Wed, 27 Nov 2013 17:38:42 -0800 Subject: [PATCH 26/67] Bug 944069 - Remove unnecessary wait during GPU Composition. r=dwilson --- widget/gonk/HwcComposer2D.cpp | 23 +++++++++++++++-------- widget/gonk/HwcComposer2D.h | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp index 13bd5fc88f9c..e668cfd4d6ff 100644 --- a/widget/gonk/HwcComposer2D.cpp +++ b/widget/gonk/HwcComposer2D.cpp @@ -63,6 +63,7 @@ HwcComposer2D::HwcComposer2D() , mHwc(nullptr) , mColorFill(false) , mRBSwapSupport(false) + , mPrevRetireFence(-1) { } @@ -492,7 +493,6 @@ HwcComposer2D::TryHwComposition() // No composition on FB layer, so closing releaseFenceFd close(mList->hwLayers[idx].releaseFenceFd); - mList->hwLayers[idx].releaseFenceFd = -1; mList->numHwLayers = 0; return true; } @@ -573,31 +573,38 @@ HwcComposer2D::Commit() int err = mHwc->set(mHwc, HWC_NUM_DISPLAY_TYPES, displays); + // To avoid tearing, workaround for missing releaseFenceFd + // waits in Gecko layers, see Bug 925444. if (!mPrevReleaseFds.IsEmpty()) { // Wait for previous retire Fence to signal. // Denotes contents on display have been replaced. // For buffer-sync, framework should not over-write // prev buffers until we close prev releaseFenceFds - sp fence = new Fence(mPrevReleaseFds[0]); + sp fence = new Fence(mPrevRetireFence); if (fence->wait(1000) == -ETIME) { - LOGE("Wait timed-out for retireFenceFd %d", mPrevReleaseFds[0]); + LOGE("Wait timed-out for retireFenceFd %d", mPrevRetireFence); } - for (int i = 0; i < mPrevReleaseFds.Length(); i++) { close(mPrevReleaseFds[i]); } + close(mPrevRetireFence); mPrevReleaseFds.Clear(); } - mPrevReleaseFds.AppendElement(mList->retireFenceFd); for (uint32_t j=0; j < (mList->numHwLayers - 1); j++) { - if (mList->hwLayers[j].compositionType == HWC_OVERLAY) { + if (mList->hwLayers[j].releaseFenceFd >= 0) { mPrevReleaseFds.AppendElement(mList->hwLayers[j].releaseFenceFd); - mList->hwLayers[j].releaseFenceFd = -1; } } - mList->retireFenceFd = -1; + if (mList->retireFenceFd >= 0) { + if (!mPrevReleaseFds.IsEmpty()) { + mPrevRetireFence = mList->retireFenceFd; + } else { // GPU Composition + close(mList->retireFenceFd); + } + } + return !err; } #else diff --git a/widget/gonk/HwcComposer2D.h b/widget/gonk/HwcComposer2D.h index 532ad5d00660..2ca4defa0f38 100644 --- a/widget/gonk/HwcComposer2D.h +++ b/widget/gonk/HwcComposer2D.h @@ -82,6 +82,7 @@ private: //to render the current frame std::list mVisibleRegions; nsTArray mPrevReleaseFds; + int mPrevRetireFence; nsTArray mHwcLayerMap; }; From cc7e21313fefaacef2f6a5d936ceb1873fa4c0b6 Mon Sep 17 00:00:00 2001 From: Shawn Ku Date: Wed, 27 Nov 2013 15:45:31 +0800 Subject: [PATCH 27/67] Bug 931722 - Part 1: RIL related code change. r=hsinyi --- dom/system/gonk/RadioInterfaceLayer.js | 7 +++++++ dom/system/gonk/nsIRadioInterfaceLayer.idl | 4 +++- dom/system/gonk/ril_worker.js | 24 ++-------------------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index f3d9d63dfa36..e6e39ddedbf6 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -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 }); + } } }; diff --git a/dom/system/gonk/nsIRadioInterfaceLayer.idl b/dom/system/gonk/nsIRadioInterfaceLayer.idl index ac3a9a46b165..62462e239d5e 100644 --- a/dom/system/gonk/nsIRadioInterfaceLayer.idl +++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl @@ -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); }; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 714b8c2fc53b..9e72ac128eda 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -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() { From 0154de919dd04fdcc9394c8056bc108314acf9c1 Mon Sep 17 00:00:00 2001 From: Shawn Ku Date: Wed, 27 Nov 2013 15:46:27 +0800 Subject: [PATCH 28/67] Bug 931722 - Part 2: Extra mute request to RIL at AudioManager. r=mchen --- dom/system/gonk/AudioManager.cpp | 30 ++++++++++++++++++++++++++---- dom/system/gonk/AudioManager.h | 3 +++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp index b54937c8a6b8..df125b9dace6 100644 --- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -14,11 +14,13 @@ */ #include +#include #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 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 diff --git a/dom/system/gonk/AudioManager.h b/dom/system/gonk/AudioManager.h index 6ac42ab62ff6..15d22fb97352 100644 --- a/dom/system/gonk/AudioManager.h +++ b/dom/system/gonk/AudioManager.h @@ -66,6 +66,9 @@ protected: private: nsAutoPtr mObserver; nsCOMPtr mPhoneAudioAgent; + bool mMuteCallToRIL; + // mIsMicMuted is only used for toggling mute call to RIL. + bool mIsMicMuted; void HandleBluetoothStatusChanged(nsISupports* aSubject, const char* aTopic, From 0e9b56329847ccd4c0bf645bf6d6050d11a01147 Mon Sep 17 00:00:00 2001 From: Shawn Ku Date: Wed, 27 Nov 2013 15:47:29 +0800 Subject: [PATCH 29/67] Bug 931722 - Part 3: Add test cases for call mute test. r=hsinyi --- dom/telephony/test/marionette/manifest.ini | 1 + .../test/marionette/test_call_mute.js | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 dom/telephony/test/marionette/test_call_mute.js diff --git a/dom/telephony/test/marionette/manifest.ini b/dom/telephony/test/marionette/manifest.ini index 32de71430022..09ad092ce5c3 100644 --- a/dom/telephony/test/marionette/manifest.ini +++ b/dom/telephony/test/marionette/manifest.ini @@ -44,3 +44,4 @@ disabled = Bug 821958 [test_emergency_label.js] [test_conference.js] [test_dsds_default_service_id.js] +[test_call_mute.js] diff --git a/dom/telephony/test/marionette/test_call_mute.js b/dom/telephony/test/marionette/test_call_mute.js new file mode 100644 index 000000000000..d97f73a126da --- /dev/null +++ b/dom/telephony/test/marionette/test_call_mute.js @@ -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(); +}); From 2f6d4e0b07eb835029521d1480938e4a5462afd7 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 2 Dec 2013 10:27:57 -0500 Subject: [PATCH 30/67] Bug 943430 - Rename geo.testing.ignore_ipc_principal to dom.testing.ignore_ipc_principal. r=ehsan --- dom/datastore/tests/test_app_install.html | 2 +- dom/datastore/tests/test_arrays.html | 12 +++--------- dom/datastore/tests/test_basic.html | 12 +++--------- dom/datastore/tests/test_bug924104.html | 12 +++--------- dom/datastore/tests/test_changes.html | 12 +++--------- dom/datastore/tests/test_oop.html | 12 +++--------- dom/datastore/tests/test_readonly.html | 2 +- dom/datastore/tests/test_sync.html | 12 +++--------- dom/ipc/ContentParent.cpp | 8 ++++---- dom/ipc/TabParent.cpp | 6 +++--- dom/tests/unit/test_geolocation_provider.js | 2 +- dom/tests/unit/test_geolocation_reset_accuracy.js | 2 +- .../unit/test_geolocation_reset_accuracy_wrap.js | 2 +- dom/tests/unit/test_geolocation_timeout.js | 2 +- dom/tests/unit/test_geolocation_timeout_wrap.js | 2 +- 15 files changed, 32 insertions(+), 68 deletions(-) diff --git a/dom/datastore/tests/test_app_install.html b/dom/datastore/tests/test_app_install.html index 9ff3c1466ccd..31e837ba58d4 100644 --- a/dom/datastore/tests/test_app_install.html +++ b/dom/datastore/tests/test_app_install.html @@ -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(); }); }); diff --git a/dom/datastore/tests/test_arrays.html b/dom/datastore/tests/test_arrays.html index 0ed14cc393a3..c25ec1eca5e7 100644 --- a/dom/datastore/tests/test_arrays.html +++ b/dom/datastore/tests/test_arrays.html @@ -76,15 +76,9 @@ // Preferences function() { - SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest); + SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true], + ["dom.datastore.enabled", true], + ["dom.testing.ignore_ipc_principal", true]]}, runTest); }, function() { diff --git a/dom/datastore/tests/test_basic.html b/dom/datastore/tests/test_basic.html index 12b3912b6eb8..6613d630d72c 100644 --- a/dom/datastore/tests/test_basic.html +++ b/dom/datastore/tests/test_basic.html @@ -76,15 +76,9 @@ // Preferences function() { - SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest); + SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true], + ["dom.datastore.enabled", true], + ["dom.testing.ignore_ipc_principal", true]]}, runTest); }, function() { diff --git a/dom/datastore/tests/test_bug924104.html b/dom/datastore/tests/test_bug924104.html index 45b673a8f3ed..1f4887aeb9ad 100644 --- a/dom/datastore/tests/test_bug924104.html +++ b/dom/datastore/tests/test_bug924104.html @@ -76,15 +76,9 @@ // Preferences function() { - SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest); + SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true], + ["dom.datastore.enabled", true], + ["dom.testing.ignore_ipc_principal", true]]}, runTest); }, function() { diff --git a/dom/datastore/tests/test_changes.html b/dom/datastore/tests/test_changes.html index ddc5c5f571f2..27a56c764384 100644 --- a/dom/datastore/tests/test_changes.html +++ b/dom/datastore/tests/test_changes.html @@ -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 diff --git a/dom/datastore/tests/test_oop.html b/dom/datastore/tests/test_oop.html index f8b22db14fff..2a6bba787183 100644 --- a/dom/datastore/tests/test_oop.html +++ b/dom/datastore/tests/test_oop.html @@ -76,15 +76,9 @@ // Preferences function() { - SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest); + SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true], + ["dom.datastore.enabled", true], + ["dom.testing.ignore_ipc_principal", true]]}, runTest); }, function() { diff --git a/dom/datastore/tests/test_readonly.html b/dom/datastore/tests/test_readonly.html index 73e8b1336f4f..833030a4b3d1 100644 --- a/dom/datastore/tests/test_readonly.html +++ b/dom/datastore/tests/test_readonly.html @@ -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); diff --git a/dom/datastore/tests/test_sync.html b/dom/datastore/tests/test_sync.html index 3a591300896a..7b21c82d80ec 100644 --- a/dom/datastore/tests/test_sync.html +++ b/dom/datastore/tests/test_sync.html @@ -76,15 +76,9 @@ // Preferences function() { - SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest); - }, - - function() { - SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest); + SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true], + ["dom.datastore.enabled", true], + ["dom.testing.ignore_ipc_principal", true]]}, runTest); }, function() { diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 18d7a011efce..4b9fcf766316 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2759,7 +2759,7 @@ ContentParent::RecvSyncMessage(const nsString& aMsg, InfallibleTArray* 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* 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) { diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index d56da7c0d21b..fed907035591 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -777,7 +777,7 @@ TabParent::RecvSyncMessage(const nsString& aMessage, { nsIPrincipal* principal = aPrincipal; ContentParent* parent = static_cast(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(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(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; } diff --git a/dom/tests/unit/test_geolocation_provider.js b/dom/tests/unit/test_geolocation_provider.js index ecae6d6f61b4..34d7803b0b39 100644 --- a/dom/tests/unit/test_geolocation_provider.js +++ b/dom/tests/unit/test_geolocation_provider.js @@ -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(); diff --git a/dom/tests/unit/test_geolocation_reset_accuracy.js b/dom/tests/unit/test_geolocation_reset_accuracy.js index e81fd762d03e..75272ab7a04e 100644 --- a/dom/tests/unit/test_geolocation_reset_accuracy.js +++ b/dom/tests/unit/test_geolocation_reset_accuracy.js @@ -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); } diff --git a/dom/tests/unit/test_geolocation_reset_accuracy_wrap.js b/dom/tests/unit/test_geolocation_reset_accuracy_wrap.js index 549b4baab0fe..84a4ac27db87 100644 --- a/dom/tests/unit/test_geolocation_reset_accuracy_wrap.js +++ b/dom/tests/unit/test_geolocation_reset_accuracy_wrap.js @@ -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); diff --git a/dom/tests/unit/test_geolocation_timeout.js b/dom/tests/unit/test_geolocation_timeout.js index 44816a35672f..a3f400b2e905 100644 --- a/dom/tests/unit/test_geolocation_timeout.js +++ b/dom/tests/unit/test_geolocation_timeout.js @@ -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); diff --git a/dom/tests/unit/test_geolocation_timeout_wrap.js b/dom/tests/unit/test_geolocation_timeout_wrap.js index 751b546d2b38..514c3f9b3a74 100644 --- a/dom/tests/unit/test_geolocation_timeout_wrap.js +++ b/dom/tests/unit/test_geolocation_timeout_wrap.js @@ -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"); } From 1f8cf1eb837e7ae5c7af53ca8b7395c7d9be2fe1 Mon Sep 17 00:00:00 2001 From: Georgia Wang Date: Mon, 18 Nov 2013 15:04:22 +0800 Subject: [PATCH 31/67] Bug 935402 - Part 1: Cache EF_PBR field. r=yoshi --- dom/system/gonk/ril_worker.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 9e72ac128eda..b48364d95103 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -11555,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), From 141cbf3ec30f5a8e1527846bff2d41af43b8dedb Mon Sep 17 00:00:00 2001 From: Georgia Wang Date: Mon, 18 Nov 2013 15:06:58 +0800 Subject: [PATCH 32/67] Bug 935402 - Part 2: Modify xpcshell test for EF_PBR. r=yoshi --- dom/system/gonk/tests/test_ril_worker_icc.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index 65ea38731a4b..48142b2a686c 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -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(); }); /** From b095f3389cb63a4bd469ce463715929c4f6c7f28 Mon Sep 17 00:00:00 2001 From: Albert Crespell Date: Fri, 29 Nov 2013 08:06:20 +0100 Subject: [PATCH 33/67] Bug 944008 - Usage reset doesn't work properly. r=gene --- dom/network/src/NetworkStatsDB.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/network/src/NetworkStatsDB.jsm b/dom/network/src/NetworkStatsDB.jsm index 066ec8e37502..9a7dc0fd5169 100644 --- a/dom/network/src/NetworkStatsDB.jsm +++ b/dom/network/src/NetworkStatsDB.jsm @@ -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; } From 41a3ceef0312e74768c413216745cc0aa1936e5e Mon Sep 17 00:00:00 2001 From: Sushil Chauhan Date: Fri, 29 Nov 2013 11:39:39 -0800 Subject: [PATCH 34/67] Bug 944207 - Reset the number of HWC layers on failures before HWC prepare. r=dwilson --- widget/gonk/HwcComposer2D.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp index e668cfd4d6ff..5b21aef7ce90 100644 --- a/widget/gonk/HwcComposer2D.cpp +++ b/widget/gonk/HwcComposer2D.cpp @@ -451,6 +451,7 @@ HwcComposer2D::TryHwComposition() if (!(fbsurface && fbsurface->lastHandle)) { LOGD("H/W Composition failed. FBSurface not initialized."); + mList->numHwLayers = 0; return false; } @@ -459,6 +460,7 @@ HwcComposer2D::TryHwComposition() if (idx >= mMaxLayerCount) { if (!ReallocLayerList() || idx >= mMaxLayerCount) { LOGE("TryHwComposition failed! Could not add FB layer"); + mList->numHwLayers = 0; return false; } } From 89e2fbba42ce7c25b04da9b92b0900832b521fb7 Mon Sep 17 00:00:00 2001 From: Gaia Pushbot Date: Mon, 2 Dec 2013 07:45:26 -0800 Subject: [PATCH 35/67] Bumping gaia.json for 2 gaia-central revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/c4922d919973 Author: Amir Nissim Desc: Merge pull request #14130 from EverythingMe/931750-typingapps-localsearch Bug 931750 - [e.me] Search "as you type" should always search locally on... ======== https://hg.mozilla.org/integration/gaia-central/rev/35f3ec8c723d Author: Amir Nissim Desc: Bug 931750 - [e.me] Search "as you type" should always search locally on device [r=evyatar, ranbena] --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index daab1a97538d..a368d4537d5b 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "e82e1705b79c349910bf66a015a681fdd0ec3500", + "revision": "c4922d919973127ccde39105deec5c87933e3b47", "repo_path": "/integration/gaia-central" } From 2c9697303dd53d86098463644cbff8c5a0e4d72e Mon Sep 17 00:00:00 2001 From: Edgar Chen Date: Tue, 26 Nov 2013 13:19:32 +0800 Subject: [PATCH 36/67] Bug 943198 - Follow-up of bug 814637: enable new marionette tests of IccManager. r=hsinyi --- dom/icc/tests/marionette/manifest.ini | 2 -- .../test_icc_access_invalid_object.js | 25 +++++++------------ .../test_icc_detected_undetected_event.js | 25 +++++++------------ 3 files changed, 18 insertions(+), 34 deletions(-) diff --git a/dom/icc/tests/marionette/manifest.ini b/dom/icc/tests/marionette/manifest.ini index cbd18fc23683..41ea70e37aa6 100644 --- a/dom/icc/tests/marionette/manifest.ini +++ b/dom/icc/tests/marionette/manifest.ini @@ -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 diff --git a/dom/icc/tests/marionette/test_icc_access_invalid_object.js b/dom/icc/tests/marionette/test_icc_access_invalid_object.js index 98628ac2196f..d55eb85ae26f 100644 --- a/dom/icc/tests/marionette/test_icc_access_invalid_object.js +++ b/dom/icc/tests/marionette/test_icc_access_invalid_object.js @@ -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 */ diff --git a/dom/icc/tests/marionette/test_icc_detected_undetected_event.js b/dom/icc/tests/marionette/test_icc_detected_undetected_event.js index a8e270569c94..919ff14630ad 100644 --- a/dom/icc/tests/marionette/test_icc_detected_undetected_event.js +++ b/dom/icc/tests/marionette/test_icc_detected_undetected_event.js @@ -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 */ From a940e05e205bef9a8ed75c4f441d8bb31ed5aeb1 Mon Sep 17 00:00:00 2001 From: Dan Glastonbury Date: Fri, 29 Nov 2013 14:16:40 +1000 Subject: [PATCH 37/67] Bug 830881 - Wrap debug functions in MOZ_ENABLE_GL_TRACKING define. r=vlad --- gfx/gl/GLContext.cpp | 3 +-- gfx/gl/GLContext.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 61587afadc0f..d401457151b9 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -2810,8 +2810,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) { diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 42eeda09a12d..1a09f014b896 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -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; @@ -3242,7 +3246,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); From 7f73b68e55860bcb7f1439ccfd240b2bc161db4a Mon Sep 17 00:00:00 2001 From: Dan Glastonbury Date: Fri, 29 Nov 2013 14:11:49 +1000 Subject: [PATCH 38/67] Bug 830881 - Port layerscope GL changes to new layers. r=vlad --- gfx/gl/GLContext.cpp | 421 +++++++++---- gfx/gl/GLContext.h | 23 +- gfx/gl/GLDefs.h | 1 + gfx/layers/Effects.h | 4 + gfx/layers/LayerScope.cpp | 590 ++++++++++++++++++ gfx/layers/LayerScope.h | 35 ++ gfx/layers/composite/CanvasLayerComposite.cpp | 3 +- gfx/layers/moz.build | 2 + gfx/layers/opengl/CompositorOGL.cpp | 10 +- modules/libpref/src/init/all.js | 4 + widget/xpwidgets/nsBaseWidget.cpp | 8 +- 11 files changed, 989 insertions(+), 112 deletions(-) create mode 100644 gfx/layers/LayerScope.cpp create mode 100644 gfx/layers/LayerScope.h diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index d401457151b9..f9d55e1e8fda 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -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 -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 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 +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 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) diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 1a09f014b896..12f01c6de341 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -2734,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. @@ -2744,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 ReadTextureImage(GLuint aTexture, + already_AddRefed ReadTextureImage(GLuint aTextureId, + GLenum aTextureTarget, const gfxIntSize& aSize, - GLenum aTextureFormat, + /* ShaderProgramType */ int aShaderProgram, bool aYInvert = false); - already_AddRefed GetTexImage(GLuint aTexture, bool aYInvert, SurfaceFormat aFormat); + already_AddRefed GetTexImage(GLuint aTexture, + bool aYInvert, + SurfaceFormat aFormat); /** * Call ReadPixels into an existing gfxImageSurface. @@ -3111,6 +3126,8 @@ public: protected: nsDataHashtable, void*> mUserData; + GLuint mReadTextureImagePrograms[4]; + bool InitWithPrefix(const char *prefix, bool trygl); void InitExtensions(); diff --git a/gfx/gl/GLDefs.h b/gfx/gl/GLDefs.h index b267834cd968..0fd805a27b2a 100644 --- a/gfx/gl/GLDefs.h +++ b/gfx/gl/GLDefs.h @@ -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 diff --git a/gfx/layers/Effects.h b/gfx/layers/Effects.h index be0a4fb12414..5199059f7432 100644 --- a/gfx/layers/Effects.h +++ b/gfx/layers/Effects.h @@ -189,8 +189,12 @@ struct EffectSolidColor : public Effect struct EffectChain { + EffectChain() : mLayerRef(NULL) {} + explicit EffectChain(void* aLayerRef) : mLayerRef(aLayerRef) {} + RefPtr mPrimaryEffect; RefPtr mSecondaryEffects[EFFECT_MAX_SECONDARY]; + void* mLayerRef; //!< For LayerScope logging }; /** diff --git a/gfx/layers/LayerScope.cpp b/gfx/layers/LayerScope.cpp new file mode 100644 index 000000000000..6ebeae9a053b --- /dev/null +++ b/gfx/layers/LayerScope.cpp @@ -0,0 +1,590 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ +#include "LayerScope.h" + +#include "mozilla/Util.h" + +#include "Composer2D.h" +#include "Effects.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Preferences.h" +#include "TexturePoolOGL.h" +#include "mozilla/layers/TextureHostOGL.h" + +#include "gfxColor.h" +#include "gfxContext.h" +#include "gfxUtils.h" +#include "gfxPlatform.h" +#include "nsIWidget.h" + +#include "GLContext.h" +#include "GLContextProvider.h" + +#include "nsIServiceManager.h" +#include "nsIConsoleService.h" + +#include +#include "mozilla/Compression.h" +#include "mozilla/LinkedList.h" +#include "nsThreadUtils.h" +#include "nsISocketTransport.h" +#include "nsIServerSocket.h" +#include "nsNetCID.h" +#include "nsIOutputStream.h" +#include "nsIEventTarget.h" +#include "nsProxyRelease.h" + +#ifdef __GNUC__ +#define PACKED_STRUCT __attribute__((packed)) +#else +#define PACKED_STRUCT +#endif + +namespace mozilla { +namespace layers { + +using namespace mozilla::Compression; +using namespace mozilla::gfx; +using namespace mozilla::gl; +using namespace mozilla; + +class DebugDataSender; + +static bool gDebugConnected = false; +static nsCOMPtr gDebugServerSocket; +static nsCOMPtr gDebugSenderThread; +static nsCOMPtr gDebugSenderTransport; +static nsCOMPtr gDebugStream; +static nsCOMPtr gCurrentSender; + +class DebugGLData : public LinkedListElement { +public: + typedef enum { + FrameStart, + FrameEnd, + TextureData, + ColorData + } DataType; + + virtual ~DebugGLData() { } + + DataType GetDataType() const { return mDataType; } + intptr_t GetContextAddress() const { return mContextAddress; } + int64_t GetValue() const { return mValue; } + + DebugGLData(DataType dataType) + : mDataType(dataType), + mContextAddress(0), + mValue(0) + { } + + DebugGLData(DataType dataType, GLContext* cx) + : mDataType(dataType), + mContextAddress(reinterpret_cast(cx)), + mValue(0) + { } + + DebugGLData(DataType dataType, GLContext* cx, int64_t value) + : mDataType(dataType), + mContextAddress(reinterpret_cast(cx)), + mValue(value) + { } + + virtual bool Write() { + if (mDataType != FrameStart && + mDataType != FrameEnd) + { + NS_WARNING("Unimplemented data type!"); + return false; + } + + DebugGLData::BasicPacket packet; + + packet.type = mDataType; + packet.ptr = static_cast(mContextAddress); + packet.value = mValue; + + return WriteToStream(&packet, sizeof(packet)); + } + + static bool WriteToStream(void *ptr, uint32_t size) { + uint32_t written = 0; + nsresult rv; + while (written < size) { + uint32_t cnt; + rv = gDebugStream->Write(reinterpret_cast(ptr) + written, + size - written, &cnt); + if (NS_FAILED(rv)) + return false; + + written += cnt; + } + + return true; + } + +protected: + DataType mDataType; + intptr_t mContextAddress; + int64_t mValue; + +public: + // the data packet formats; all packed +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif + typedef struct { + uint32_t type; + uint64_t ptr; + uint64_t value; + } PACKED_STRUCT BasicPacket; + + typedef struct { + uint32_t type; + uint64_t ptr; + uint64_t layerref; + uint32_t color; + uint32_t width; + uint32_t height; + } PACKED_STRUCT ColorPacket; + + typedef struct { + uint32_t type; + uint64_t ptr; + uint64_t layerref; + uint32_t name; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t format; + uint32_t target; + uint32_t dataFormat; + uint32_t dataSize; + } PACKED_STRUCT TexturePacket; +#ifdef _MSC_VER +#pragma pack(pop) +#endif +}; + +class DebugGLTextureData : public DebugGLData { +public: + DebugGLTextureData(GLContext* cx, void* layerRef, GLuint target, GLenum name, gfxImageSurface* img) + : DebugGLData(DebugGLData::TextureData, cx), + mLayerRef(layerRef), + mTarget(target), + mName(name), + mImage(img) + { } + + void *GetLayerRef() const { return mLayerRef; } + GLuint GetName() const { return mName; } + gfxImageSurface* GetImage() const { return mImage; } + GLenum GetTextureTarget() const { return mTarget; } + + virtual bool Write() { + DebugGLData::TexturePacket packet; + char* dataptr = nullptr; + uint32_t datasize = 0; + std::auto_ptr compresseddata; + + packet.type = mDataType; + packet.ptr = static_cast(mContextAddress); + packet.layerref = reinterpret_cast(mLayerRef); + packet.name = mName; + packet.format = 0; + packet.target = mTarget; + packet.dataFormat = LOCAL_GL_RGBA; + + if (mImage) { + packet.width = mImage->Width(); + packet.height = mImage->Height(); + packet.stride = mImage->Stride(); + packet.dataSize = mImage->GetDataSize(); + + dataptr = (char*) mImage->Data(); + datasize = mImage->GetDataSize(); + + compresseddata = std::auto_ptr((char*) moz_malloc(LZ4::maxCompressedSize(datasize))); + if (compresseddata.get()) { + int ndatasize = LZ4::compress(dataptr, datasize, compresseddata.get()); + if (ndatasize > 0) { + datasize = ndatasize; + dataptr = compresseddata.get(); + + packet.dataFormat = (1 << 16) | packet.dataFormat; + packet.dataSize = datasize; + } + } + } else { + packet.width = 0; + packet.height = 0; + packet.stride = 0; + packet.dataSize = 0; + } + + // write the packet header data + if (!WriteToStream(&packet, sizeof(packet))) + return false; + + // then the image data + if (!WriteToStream(dataptr, datasize)) + return false; + + // then pad out to 4 bytes + if (datasize % 4 != 0) { + static char buf[] = { 0, 0, 0, 0 }; + if (!WriteToStream(buf, 4 - (datasize % 4))) + return false; + } + + return true; + } + +protected: + void* mLayerRef; + GLenum mTarget; + GLuint mName; + nsRefPtr mImage; +}; + +class DebugGLColorData : public DebugGLData { +public: + DebugGLColorData(void* layerRef, const gfxRGBA& color, int width, int height) + : DebugGLData(DebugGLData::ColorData), + mColor(color.Packed()), + mSize(width, height) + { } + + void *GetLayerRef() const { return mLayerRef; } + uint32_t GetColor() const { return mColor; } + const nsIntSize& GetSize() const { return mSize; } + + virtual bool Write() { + DebugGLData::ColorPacket packet; + + packet.type = mDataType; + packet.ptr = static_cast(mContextAddress); + packet.layerref = reinterpret_cast(mLayerRef); + packet.color = mColor; + packet.width = mSize.width; + packet.height = mSize.height; + + return WriteToStream(&packet, sizeof(packet)); + } + +protected: + void *mLayerRef; + uint32_t mColor; + nsIntSize mSize; +}; + +static bool +CheckSender() +{ + if (!gDebugConnected) + return false; + + // At some point we may want to support sending + // data in between frames. +#if 1 + if (!gCurrentSender) + return false; +#else + if (!gCurrentSender) + gCurrentSender = new DebugDataSender(); +#endif + + return true; +} + + +class DebugListener : public nsIServerSocketListener +{ +public: + + NS_DECL_THREADSAFE_ISUPPORTS + + DebugListener() { } + virtual ~DebugListener() { } + + /* nsIServerSocketListener */ + + NS_IMETHODIMP OnSocketAccepted(nsIServerSocket *aServ, + nsISocketTransport *aTransport) + { + printf_stderr("*** LayerScope: Accepted connection\n"); + gDebugConnected = true; + gDebugSenderTransport = aTransport; + aTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(gDebugStream)); + return NS_OK; + } + + NS_IMETHODIMP OnStopListening(nsIServerSocket *aServ, + nsresult aStatus) + { + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS1(DebugListener, nsIServerSocketListener); + + +class DebugDataSender : public nsIRunnable +{ +public: + + NS_DECL_THREADSAFE_ISUPPORTS + + DebugDataSender() { + mList = new LinkedList(); + } + + virtual ~DebugDataSender() { + Cleanup(); + } + + void Append(DebugGLData *d) { + mList->insertBack(d); + } + + void Cleanup() { + if (!mList) + return; + + DebugGLData *d; + while ((d = mList->popFirst()) != nullptr) + delete d; + delete mList; + + mList = nullptr; + } + + /* nsIRunnable impl; send the data */ + + NS_IMETHODIMP Run() { + DebugGLData *d; + nsresult rv = NS_OK; + + // If we got closed while trying to write some stuff earlier, just + // throw away everything. + if (!gDebugStream) { + Cleanup(); + return NS_OK; + } + + while ((d = mList->popFirst()) != nullptr) { + std::auto_ptr cleaner(d); + if (!d->Write()) { + rv = NS_ERROR_FAILURE; + break; + } + } + + Cleanup(); + + if (NS_FAILED(rv)) { + gDebugSenderTransport->Close(rv); + gDebugConnected = false; + gDebugStream = nullptr; + gDebugServerSocket = nullptr; + } + + return NS_OK; + } + +protected: + LinkedList *mList; +}; + +NS_IMPL_ISUPPORTS1(DebugDataSender, nsIRunnable); + +void +LayerScope::CreateServerSocket() +{ + if (!Preferences::GetBool("gfx.layerscope.enabled", false)) { + return; + } + + if (!gDebugSenderThread) { + NS_NewThread(getter_AddRefs(gDebugSenderThread)); + } + + if (!gDebugServerSocket) { + gDebugServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); + int port = Preferences::GetInt("gfx.layerscope.port", 23456); + gDebugServerSocket->Init(port, false, -1); + gDebugServerSocket->AsyncListen(new DebugListener); + } +} + +void +LayerScope::DestroyServerSocket() +{ + gDebugConnected = false; + gDebugStream = nullptr; + gDebugServerSocket = nullptr; +} + +void +LayerScope::BeginFrame(GLContext* aGLContext, int64_t aFrameStamp) +{ + if (!gDebugConnected) + return; + +#if 0 + // if we're sending data in between frames, flush the list down the socket, + // and start a new one + if (gCurrentSender) { + gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL); + } +#endif + + gCurrentSender = new DebugDataSender(); + gCurrentSender->Append(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp)); +} + +void +LayerScope::EndFrame(GLContext* aGLContext) +{ + if (!CheckSender()) + return; + + gCurrentSender->Append(new DebugGLData(DebugGLData::FrameEnd, aGLContext)); + gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL); + gCurrentSender = nullptr; +} + +static void +SendColor(void* aLayerRef, const gfxRGBA& aColor, int aWidth, int aHeight) +{ + if (!CheckSender()) + return; + + gCurrentSender->Append( + new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight)); +} + +static void +SendTextureSource(GLContext* aGLContext, + void* aLayerRef, + TextureSourceOGL* aSource, + bool aFlipY) +{ + GLenum textureTarget = aSource->GetTextureTarget(); + int shaderProgram = + (int) ShaderProgramFromTargetAndFormat(textureTarget, + aSource->GetFormat()); + + aSource->BindTexture(LOCAL_GL_TEXTURE0); + + GLuint textureId = 0; + // This is horrid hack. It assumes that aGLContext matches the context + // aSource has bound to. + if (textureTarget == LOCAL_GL_TEXTURE_2D) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &textureId); + } else if (textureTarget == LOCAL_GL_TEXTURE_EXTERNAL) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &textureId); + } else if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &textureId); + } + + gfx::IntSize size = aSource->GetSize(); + + // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding + // texture correctly. textureId is used for tracking in DebugGLTextureData. + nsRefPtr img = + aGLContext->ReadTextureImage(0, textureTarget, + gfxIntSize(size.width, size.height), + shaderProgram, aFlipY); + + gCurrentSender->Append( + new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, + textureId, img)); +} + +static void +SendTexturedEffect(GLContext* aGLContext, + void* aLayerRef, + const TexturedEffect* aEffect) +{ + TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL(); + if (!source) + return; + + bool flipY = false; + SendTextureSource(aGLContext, aLayerRef, source, flipY); +} + +static void +SendYCbCrEffect(GLContext* aGLContext, + void* aLayerRef, + const EffectYCbCr* aEffect) +{ + TextureSource* sourceYCbCr = aEffect->mTexture; + if (!sourceYCbCr) + return; + + const int Y = 0, Cb = 1, Cr = 2; + TextureSourceOGL* sourceY = sourceYCbCr->GetSubSource(Y)->AsSourceOGL(); + TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(); + TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL(); + + bool flipY = false; + SendTextureSource(aGLContext, aLayerRef, sourceY, flipY); + SendTextureSource(aGLContext, aLayerRef, sourceCb, flipY); + SendTextureSource(aGLContext, aLayerRef, sourceCr, flipY); +} + +void +LayerScope::SendEffectChain(GLContext* aGLContext, + const EffectChain& aEffectChain, + int aWidth, int aHeight) +{ + if (!CheckSender()) + return; + + const Effect* primaryEffect = aEffectChain.mPrimaryEffect; + switch (primaryEffect->mType) { + case EFFECT_BGRX: + case EFFECT_RGBX: + case EFFECT_BGRA: + case EFFECT_RGBA: + { + const TexturedEffect* texturedEffect = + static_cast(primaryEffect); + SendTexturedEffect(aGLContext, aEffectChain.mLayerRef, texturedEffect); + } + break; + case EFFECT_YCBCR: + { + const EffectYCbCr* yCbCrEffect = + static_cast(primaryEffect); + SendYCbCrEffect(aGLContext, aEffectChain.mLayerRef, yCbCrEffect); + } + case EFFECT_SOLID_COLOR: + { + const EffectSolidColor* solidColorEffect = + static_cast(primaryEffect); + gfxRGBA color(solidColorEffect->mColor.r, + solidColorEffect->mColor.g, + solidColorEffect->mColor.b, + solidColorEffect->mColor.a); + SendColor(aEffectChain.mLayerRef, color, aWidth, aHeight); + } + break; + case EFFECT_COMPONENT_ALPHA: + case EFFECT_RENDER_TARGET: + default: + break; + } + + //const Effect* secondaryEffect = aEffectChain.mSecondaryEffects[EFFECT_MASK]; + // TODO: +} + +} /* layers */ +} /* mozilla */ diff --git a/gfx/layers/LayerScope.h b/gfx/layers/LayerScope.h new file mode 100644 index 000000000000..a8ac4581fbba --- /dev/null +++ b/gfx/layers/LayerScope.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 GFX_LAYERSCOPE_H +#define GFX_LAYERSCOPE_H + +#include + +struct nsIntSize; + +namespace mozilla { + +namespace gl { class GLContext; } + +namespace layers { + +struct EffectChain; + +class LayerScope { +public: + static void CreateServerSocket(); + static void DestroyServerSocket(); + static void BeginFrame(gl::GLContext* aGLContext, int64_t aFrameStamp); + static void EndFrame(gl::GLContext* aGLContext); + static void SendEffectChain(gl::GLContext* aGLContext, + const EffectChain& aEffectChain, + int aWidth, int aHeight); +}; + +} /* layers */ +} /* mozilla */ + +#endif /* GFX_LAYERSCOPE_H */ diff --git a/gfx/layers/composite/CanvasLayerComposite.cpp b/gfx/layers/composite/CanvasLayerComposite.cpp index 0c563294db7a..7c28082721c9 100644 --- a/gfx/layers/composite/CanvasLayerComposite.cpp +++ b/gfx/layers/composite/CanvasLayerComposite.cpp @@ -93,7 +93,8 @@ CanvasLayerComposite::RenderLayer(const nsIntRect& aClipRect) } #endif - EffectChain effectChain; + EffectChain effectChain(this); + LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(mMaskLayer, effectChain); gfx::Matrix4x4 transform; ToMatrix4x4(GetEffectiveTransform(), transform); diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 604bfa3bff7b..1b2c35381d83 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -28,6 +28,7 @@ EXPORTS += [ 'ipc/CompositorParent.h', 'ipc/ShadowLayersManager.h', 'Layers.h', + 'LayerScope.h', 'LayersLogging.h', 'LayerSorter.h', 'LayerTreeInvalidation.h', @@ -251,6 +252,7 @@ UNIFIED_SOURCES += [ 'ipc/SharedRGBImage.cpp', 'ipc/TaskThrottler.cpp', 'Layers.cpp', + 'LayerScope.cpp', 'LayersLogging.cpp', 'LayerSorter.cpp', 'LayerTreeInvalidation.cpp', diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 25687f96612a..49deffeabfa3 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -11,6 +11,7 @@ #include "GLContextProvider.h" // for GLContextProvider #include "GLContext.h" // for GLContext #include "Layers.h" // for WriteSnapshotToDumpFile +#include "LayerScope.h" // for LayerScope #include "gfx2DGlue.h" // for ThebesFilter #include "gfx3DMatrix.h" // for gfx3DMatrix #include "gfxASurface.h" // for gfxASurface, etc @@ -50,7 +51,7 @@ #endif #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) - + namespace mozilla { using namespace gfx; @@ -767,6 +768,8 @@ CompositorOGL::BeginFrame(const nsIntRegion& aInvalidRegion, PROFILER_LABEL("CompositorOGL", "BeginFrame"); MOZ_ASSERT(!mFrameInProgress, "frame still in progress (should have called EndFrame or AbortFrame"); + LayerScope::BeginFrame(mGLContext, PR_Now()); + mVBOs.Reset(); mFrameInProgress = true; @@ -1014,6 +1017,9 @@ CompositorOGL::DrawQuad(const Rect& aRect, mGLContext->PushScissorRect(nsIntRect(intClipRect.x, intClipRect.y, intClipRect.width, intClipRect.height)); + LayerScope::SendEffectChain(mGLContext, aEffectChain, + aRect.width, aRect.height); + MaskType maskType; EffectMask* effectMask; TextureSourceOGL* sourceMask = nullptr; @@ -1308,6 +1314,8 @@ CompositorOGL::EndFrame() mFrameInProgress = false; + LayerScope::EndFrame(mGLContext); + if (mTarget) { CopyToTarget(mTarget, mCurrentRenderTarget->GetTransform()); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 6112b979f272..5ae714635fc6 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -308,6 +308,10 @@ pref("apz.axis_lock_mode", 0); pref("gfx.hidpi.enabled", 2); #endif +// Whether to enable LayerScope tool and default listening port +pref("gfx.layerscope.enabled", false); +pref("gfx.layerscope.port", 23456); + // 0 = Off, 1 = Full, 2 = Tagged Images Only. // See eCMSMode in gfx/thebes/gfxPlatform.h pref("gfx.color_management.mode", 2); diff --git a/widget/xpwidgets/nsBaseWidget.cpp b/widget/xpwidgets/nsBaseWidget.cpp index 5dcdd09a0617..8e6b11899b2f 100644 --- a/widget/xpwidgets/nsBaseWidget.cpp +++ b/widget/xpwidgets/nsBaseWidget.cpp @@ -41,6 +41,7 @@ #include "mozilla/gfx/2D.h" #include "mozilla/MouseEvents.h" #include "GLConsts.h" +#include "LayerScope.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -161,6 +162,8 @@ static void DeferredDestroyCompositor(CompositorParent* aCompositorParent, void nsBaseWidget::DestroyCompositor() { + LayerScope::DestroyServerSocket(); + if (mCompositorChild) { mCompositorChild->SendWillStop(); mCompositorChild->Destroy(); @@ -749,7 +752,7 @@ NS_IMETHODIMP nsBaseWidget::MakeFullScreen(bool aFullScreen) if (!mOriginalBounds) mOriginalBounds = new nsIntRect(); GetScreenBounds(*mOriginalBounds); - // convert dev pix to display pix for window manipulation + // convert dev pix to display pix for window manipulation CSSToLayoutDeviceScale scale = GetDefaultScale(); mOriginalBounds->x = NSToIntRound(mOriginalBounds->x / scale.scale); mOriginalBounds->y = NSToIntRound(mOriginalBounds->y / scale.scale); @@ -950,6 +953,9 @@ void nsBaseWidget::CreateCompositor(int aWidth, int aHeight) return; } + // The server socket has to be created on the main thread. + LayerScope::CreateServerSocket(); + mCompositorParent = NewCompositorParent(aWidth, aHeight); MessageChannel *parentChannel = mCompositorParent->GetIPCChannel(); ClientLayerManager* lm = new ClientLayerManager(this); From b37f4cce7cefdbd133da7e483b58384b7e33f710 Mon Sep 17 00:00:00 2001 From: Carmen Jimenez Date: Thu, 21 Nov 2013 17:56:15 +0100 Subject: [PATCH 39/67] Bug 935924 - [SingleVariant] 3rd party apps are deleted after a factory reset. r=fabrice --- b2g/app/b2g.js | 5 ++ dom/apps/src/OperatorApps.jsm | 149 +++++++++++++++++++++++++++------- 2 files changed, 125 insertions(+), 29 deletions(-) diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index b6206fbae632..7a77f5ee0b82 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -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); diff --git a/dom/apps/src/OperatorApps.jsm b/dom/apps/src/OperatorApps.jsm index eb4d082f2b4f..9870ca1d2d1e 100644 --- a/dom/apps/src/OperatorApps.jsm +++ b/dom/apps/src/OperatorApps.jsm @@ -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; }, From 2d12d37e18b851add5bcc69eeee0fa28a306325a Mon Sep 17 00:00:00 2001 From: Kevin Simons Date: Mon, 2 Dec 2013 11:03:13 -0500 Subject: [PATCH 40/67] Bug 944412 - Fix an issue with the stride in SourceSurfaceSkia::InitFromData. r=gal After calling SkBitmap::copyTo, the InitFromData method assumed that the stride of the destination SkBitmap was now the same as the stride of the source bitmap. This was, however, not the case. Now the stride is read back out of the destination bitmap. This was causing a crash due to memory corruption for FORMAT_B8G8R8X8 surfaces. --- gfx/2d/SourceSurfaceSkia.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gfx/2d/SourceSurfaceSkia.cpp b/gfx/2d/SourceSurfaceSkia.cpp index 9e04541c8aaa..9261df1eadb7 100644 --- a/gfx/2d/SourceSurfaceSkia.cpp +++ b/gfx/2d/SourceSurfaceSkia.cpp @@ -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(mBitmap.getPixels()), aSize, aStride); + ConvertBGRXToBGRA(reinterpret_cast(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; } From 5c64be4f5b100fb2172b1cc7b2ab0a3430c9c8a7 Mon Sep 17 00:00:00 2001 From: Shelly Lin Date: Tue, 12 Nov 2013 10:29:09 +0800 Subject: [PATCH 41/67] Bug 945135 - Part 1: Refactor of TrackEncoder and AudioTrackEncoder. r=roc --- content/media/encoder/EncodedFrameContainer.h | 1 + content/media/encoder/OpusTrackEncoder.cpp | 32 ++-- content/media/encoder/OpusTrackEncoder.h | 11 +- content/media/encoder/TrackEncoder.cpp | 55 ++----- content/media/encoder/TrackEncoder.h | 149 ++++++++++-------- 5 files changed, 127 insertions(+), 121 deletions(-) diff --git a/content/media/encoder/EncodedFrameContainer.h b/content/media/encoder/EncodedFrameContainer.h index 8487c92375b8..6b665f8c305a 100644 --- a/content/media/encoder/EncodedFrameContainer.h +++ b/content/media/encoder/EncodedFrameContainer.h @@ -6,6 +6,7 @@ #ifndef EncodedFrameContainer_H_ #define EncodedFrameContainer_H_ +#include "nsAutoPtr.h" #include "nsTArray.h" namespace mozilla { diff --git a/content/media/encoder/OpusTrackEncoder.cpp b/content/media/encoder/OpusTrackEncoder.cpp index da087290faec..38ce214f72ae 100644 --- a/content/media/encoder/OpusTrackEncoder.cpp +++ b/content/media/encoder/OpusTrackEncoder.cpp @@ -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 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; diff --git a/content/media/encoder/OpusTrackEncoder.h b/content/media/encoder/OpusTrackEncoder.h index 4ed44d46f694..27abc3420e82 100644 --- a/content/media/encoder/OpusTrackEncoder.h +++ b/content/media/encoder/OpusTrackEncoder.h @@ -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 mSourceSegment; + AudioSegment mSourceSegment; /** * Total samples of delay added by codec, can be queried by the encoder. From diff --git a/content/media/encoder/TrackEncoder.cpp b/content/media/encoder/TrackEncoder.cpp index 4065df8f08d9..46937baf8bfb 100644 --- a/content/media/encoder/TrackEncoder.cpp +++ b/content/media/encoder/TrackEncoder.cpp @@ -3,21 +3,22 @@ * 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 -#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; void AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, @@ -31,12 +32,11 @@ AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, return; } - AudioSegment* audio = const_cast - (static_cast(&aQueuedMedia)); + const AudioSegment& audio = static_cast(aQueuedMedia); // Check and initialize parameters for codec encoder. if (!mInitialized) { - AudioSegment::ChunkIterator iter(*audio); + AudioSegment::ChunkIterator iter(const_cast(audio)); while (!iter.IsEnded()) { AudioChunk chunk = *iter; @@ -49,17 +49,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 +66,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 +81,19 @@ AudioTrackEncoder::NotifyEndOfStream() } nsresult -AudioTrackEncoder::AppendAudioSegment(MediaSegment* aSegment) +AudioTrackEncoder::AppendAudioSegment(const AudioSegment& aSegment) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); - AudioSegment* audio = static_cast(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(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(); } diff --git a/content/media/encoder/TrackEncoder.h b/content/media/encoder/TrackEncoder.h index f4ac1fdd8b3d..2957233d8238 100644 --- a/content/media/encoder/TrackEncoder.h +++ b/content/media/encoder/TrackEncoder.h @@ -9,9 +9,9 @@ #include "mozilla/ReentrantMonitor.h" #include "AudioSegment.h" +#include "EncodedFrameContainer.h" #include "StreamBuffer.h" #include "TrackMetadataBase.h" -#include "EncodedFrameContainer.h" namespace mozilla { @@ -30,7 +30,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 +55,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 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 +86,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 +170,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,38 +194,16 @@ 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 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 From 36d152599770f900c92bf0acc84fb97d0aefcc8d Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Mon, 2 Dec 2013 11:34:47 -0500 Subject: [PATCH 42/67] Bug 762083 - Avoid places database growth for webapps. r=mak --- toolkit/components/places/Database.cpp | 14 +++++++++++--- webapprt/prefs.js | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/toolkit/components/places/Database.cpp b/toolkit/components/places/Database.cpp index 9825ad71e991..e825cbb0f45e 100644 --- a/toolkit/components/places/Database.cpp +++ b/toolkit/components/places/Database.cpp @@ -39,12 +39,15 @@ // Set when the database file was found corrupt by a previous maintenance. #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup" +// Set to specify the size of the places database growth increments in kibibytes +#define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB" + // Maximum size for the WAL file. It should be small enough since in case of // crashes we could lose all the transactions in the file. But a too small // file could hurt performance. #define DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES 512 -#define BYTES_PER_MEBIBYTE 1048576 +#define BYTES_PER_KIBIBYTE 1024 // Old Sync GUID annotation. #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid") @@ -592,8 +595,13 @@ Database::InitSchema(bool* aDatabaseMigrated) journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3); (void)mMainConn->ExecuteSimpleSQL(journalSizePragma); - // Grow places in 10MiB increments to limit fragmentation on disk. - (void)mMainConn->SetGrowthIncrement(10 * BYTES_PER_MEBIBYTE, EmptyCString()); + // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk. + // By default, it's 10 MB. + int32_t growthIncrementKiB = + Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 10 * BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE); + if (growthIncrementKiB > 0) { + (void)mMainConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString()); + } // We use our functions during migration, so initialize them now. rv = InitFunctions(); diff --git a/webapprt/prefs.js b/webapprt/prefs.js index b8b151c2908a..814d766641f9 100644 --- a/webapprt/prefs.js +++ b/webapprt/prefs.js @@ -83,3 +83,4 @@ pref("dom.ipc.plugins.enabled.x86_64", true); pref("dom.ipc.plugins.enabled", true); #endif +pref("places.database.growthIncrementKiB", 0); From 3aa17893dc2b13d0bd80aca08cd722d697dd9973 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Mon, 2 Dec 2013 11:34:47 -0500 Subject: [PATCH 43/67] Bug 931921 - Prevent creating multiple intances of global actors. r=past --- .../debugger/test/browser_dbg_globalactor.js | 27 ++++++------------- toolkit/devtools/server/actors/root.js | 6 ++++- .../server/tests/unit/test_add_actors.js | 20 ++++++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_globalactor.js b/browser/devtools/debugger/test/browser_dbg_globalactor.js index 3e13c7ab9e30..12cc4d50ed88 100644 --- a/browser/devtools/debugger/test/browser_dbg_globalactor.js +++ b/browser/devtools/debugger/test/browser_dbg_globalactor.js @@ -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); }); diff --git a/toolkit/devtools/server/actors/root.js b/toolkit/devtools/server/actors/root.js index bd8a7e61ff6e..efd1c1cb4c06 100644 --- a/toolkit/devtools/server/actors/root.js +++ b/toolkit/devtools/server/actors/root.js @@ -253,7 +253,11 @@ RootActor.prototype = { } /* DebuggerServer.addGlobalActor support: create actors. */ - this._createExtraActors(this._parameters.globalActorFactories, newActorPool); + if (!this._globalActorPool) { + this._globalActorPool = new ActorPool(this.conn); + this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool); + this.conn.addActorPool(this._globalActorPool); + } /* * Drop the old actorID -> actor map. Actors that still mattered were diff --git a/toolkit/devtools/server/tests/unit/test_add_actors.js b/toolkit/devtools/server/tests/unit/test_add_actors.js index 8fb1c634310f..3bdbb8adc9ce 100644 --- a/toolkit/devtools/server/tests/unit/test_add_actors.js +++ b/toolkit/devtools/server/tests/unit/test_add_actors.js @@ -27,6 +27,7 @@ function run_test() add_test(test_pre_init_tab_actor); add_test(test_post_init_global_actor); add_test(test_post_init_tab_actor); + add_test(test_stable_global_actor_instances); add_test(close_client); run_next_test(); } @@ -82,6 +83,25 @@ function test_post_init_tab_actor() ); } +// Get the object object, from the server side, for a given actor ID +function getActorInstance(connID, actorID) { + return DebuggerServer._connections[connID].getActor(actorID); +} + +function test_stable_global_actor_instances() +{ + // Consider that there is only one connection, + // and the first one is ours + let connID = Object.keys(DebuggerServer._connections)[0]; + let postInitGlobalActor = getActorInstance(connID, gActors.postInitGlobalActor); + let preInitGlobalActor = getActorInstance(connID, gActors.preInitGlobalActor); + gClient.listTabs(function onListTabs(aResponse) { + do_check_eq(postInitGlobalActor, getActorInstance(connID, aResponse.postInitGlobalActor)); + do_check_eq(preInitGlobalActor, getActorInstance(connID, aResponse.preInitGlobalActor)); + run_next_test(); + }); +} + function close_client() { gClient.close(() => run_next_test()); } From 513888aae8e3553d0a2d89a2fa7622e7be9f350d Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Tue, 26 Nov 2013 12:45:34 +1100 Subject: [PATCH 44/67] Bug 939643 - Avoid test orange by rejigging how we wait for blocklist window. r=mixedpuppy --- .../content/test/social/browser_blocklist.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js index b8143b8b0d33..bc8d03ae0a43 100644 --- a/browser/base/content/test/social/browser_blocklist.js +++ b/browser/base/content/test/social/browser_blocklist.js @@ -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) { } From f9aac2edf0e5b86a3c805293a692f7f134426204 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Mon, 2 Dec 2013 11:34:47 -0500 Subject: [PATCH 45/67] Bug 940538 - Convert network monitor to use Promise.jsm. r=benvie, r=vp --- .../netmonitor/netmonitor-controller.js | 19 +- .../devtools/netmonitor/netmonitor-panel.js | 2 +- .../devtools/netmonitor/netmonitor-view.js | 190 +++++++++++------- .../test/browser_net_content-type.js | 54 ++--- .../test/browser_net_cyrillic-01.js | 5 +- .../test/browser_net_cyrillic-02.js | 5 +- .../test/browser_net_json-malformed.js | 59 +++--- .../test/browser_net_json_custom_mime.js | 7 +- .../netmonitor/test/browser_net_jsonp.js | 7 +- .../test/browser_net_post-data-01.js | 16 +- .../test/browser_net_post-data-02.js | 62 +++--- .../netmonitor/test/browser_net_resend.js | 34 ++-- .../browser_net_simple-request-details.js | 53 ++--- browser/devtools/netmonitor/test/head.js | 20 +- 14 files changed, 318 insertions(+), 215 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js index 143bffe1e1d8..ba38e1758658 100644 --- a/browser/devtools/netmonitor/netmonitor-controller.js +++ b/browser/devtools/netmonitor/netmonitor-controller.js @@ -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"); diff --git a/browser/devtools/netmonitor/netmonitor-panel.js b/browser/devtools/netmonitor/netmonitor-panel.js index 5f5991e3bebc..1efb4c409282 100644 --- a/browser/devtools/netmonitor/netmonitor-panel.js +++ b/browser/devtools/netmonitor/netmonitor-panel.js @@ -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) { diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index 6cca63cfe007..54692152cec9 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -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)); }, /** diff --git a/browser/devtools/netmonitor/test/browser_net_content-type.js b/browser/devtools/netmonitor/test/browser_net_content-type.js index bbec1d7c0e06..75d6cc0f301a 100644 --- a/browser/devtools/netmonitor/test/browser_net_content-type.js +++ b/browser/devtools/netmonitor/test/browser_net_content-type.js @@ -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(); diff --git a/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js b/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js index 31180dcf0551..3bf6a97f23a7 100644 --- a/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js +++ b/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js @@ -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, diff --git a/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js b/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js index fd31e5fd8bf3..4fc8020d7241 100644 --- a/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js +++ b/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js @@ -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, diff --git a/browser/devtools/netmonitor/test/browser_net_json-malformed.js b/browser/devtools/netmonitor/test/browser_net_json-malformed.js index 4115ec3ed9b2..e1c75870d829 100644 --- a/browser/devtools/netmonitor/test/browser_net_json-malformed.js +++ b/browser/devtools/netmonitor/test/browser_net_json-malformed.js @@ -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); + }); }); }); diff --git a/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js b/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js index 0faa71490651..340c3307e2a4 100644 --- a/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js +++ b/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js @@ -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]; diff --git a/browser/devtools/netmonitor/test/browser_net_jsonp.js b/browser/devtools/netmonitor/test/browser_net_jsonp.js index a089bb58096f..f7b850d02eda 100644 --- a/browser/devtools/netmonitor/test/browser_net_jsonp.js +++ b/browser/devtools/netmonitor/test/browser_net_jsonp.js @@ -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]; diff --git a/browser/devtools/netmonitor/test/browser_net_post-data-01.js b/browser/devtools/netmonitor/test/browser_net_post-data-01.js index 0607279fdb0c..661606174d53 100644 --- a/browser/devtools/netmonitor/test/browser_net_post-data-01.js +++ b/browser/devtools/netmonitor/test/browser_net_post-data-01.js @@ -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) { diff --git a/browser/devtools/netmonitor/test/browser_net_post-data-02.js b/browser/devtools/netmonitor/test/browser_net_post-data-02.js index ea3c175e213e..3685cab8c85a 100644 --- a/browser/devtools/netmonitor/test/browser_net_post-data-02.js +++ b/browser/devtools/netmonitor/test/browser_net_post-data-02.js @@ -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(); diff --git a/browser/devtools/netmonitor/test/browser_net_resend.js b/browser/devtools/netmonitor/test/browser_net_resend.js index 2d9b52acd9b9..cb3f196e981e 100644 --- a/browser/devtools/netmonitor/test/browser_net_resend.js +++ b/browser/devtools/netmonitor/test/browser_net_resend.js @@ -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(); }); }); diff --git a/browser/devtools/netmonitor/test/browser_net_simple-request-details.js b/browser/devtools/netmonitor/test/browser_net_simple-request-details.js index 948025d3a1d7..af9850608c3e 100644 --- a/browser/devtools/netmonitor/test/browser_net_simple-request-details.js +++ b/browser/devtools/netmonitor/test/browser_net_simple-request-details.js @@ -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, diff --git a/browser/devtools/netmonitor/test/head.js b/browser/devtools/netmonitor/test/head.js index a9cf66420453..0fa11ec0704e 100644 --- a/browser/devtools/netmonitor/test/head.js +++ b/browser/devtools/netmonitor/test/head.js @@ -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; +} From de6d743a5c297dc42fb1a7a18d7c80fb24c3b319 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Mon, 2 Dec 2013 11:34:47 -0500 Subject: [PATCH 46/67] Bug 941012 - Always use SDK loader to load DebuggerServer. r=jryans --- toolkit/devtools/Loader.jsm | 1 + toolkit/devtools/server/dbg-server.jsm | 30 ++---------- toolkit/devtools/server/main.js | 64 ++++++++++++-------------- 3 files changed, 35 insertions(+), 60 deletions(-) diff --git a/toolkit/devtools/Loader.jsm b/toolkit/devtools/Loader.jsm index d2c8bb034730..530831976ae7 100644 --- a/toolkit/devtools/Loader.jsm +++ b/toolkit/devtools/Loader.jsm @@ -35,6 +35,7 @@ let loaderGlobals = { btoa: btoa, console: console, _Iterator: Iterator, + ChromeWorker: ChromeWorker, loader: { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils), lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils), diff --git a/toolkit/devtools/server/dbg-server.jsm b/toolkit/devtools/server/dbg-server.jsm index 44d88f3c3195..4087d6ab4ec6 100644 --- a/toolkit/devtools/server/dbg-server.jsm +++ b/toolkit/devtools/server/dbg-server.jsm @@ -15,31 +15,11 @@ const Ci = Components.interfaces; const Cc = Components.classes; const Cu = Components.utils; +const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); + this.EXPORTED_SYMBOLS = ["DebuggerServer", "ActorPool"]; -var loadSubScript = - "function loadSubScript(aURL)\n" + - "{\n" + - "const Ci = Components.interfaces;\n" + - "const Cc = Components.classes;\n" + - " try {\n" + - " let loader = Cc[\"@mozilla.org/moz/jssubscript-loader;1\"]\n" + - " .getService(Ci.mozIJSSubScriptLoader);\n" + - " loader.loadSubScript(aURL, this);\n" + - " } catch(e) {\n" + - " dump(\"Error loading: \" + aURL + \": \" + e + \" - \" + e.stack + \"\\n\");\n" + - " throw e;\n" + - " }\n" + - "}"; +let server = devtools.require("devtools/server/main"); -// Load the debugging server in a sandbox with its own compartment. -var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] - .createInstance(Ci.nsIPrincipal); - -var gGlobal = Cu.Sandbox(systemPrincipal); -gGlobal.ChromeWorker = ChromeWorker; -Cu.evalInSandbox(loadSubScript, gGlobal, "1.8"); -gGlobal.loadSubScript("resource://gre/modules/devtools/server/main.js"); - -this.DebuggerServer = gGlobal.DebuggerServer; -this.ActorPool = gGlobal.ActorPool; +this.DebuggerServer = server.DebuggerServer; +this.ActorPool = server.ActorPool; diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js index 01f78e7991c8..ae2f86788ccc 100644 --- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -10,36 +10,22 @@ * debugging global. */ -// |this.require| is used to test if this file was loaded via the devtools -// loader (as it is in DebuggerProcess.jsm) or via loadSubScript (as it is from -// dbg-server.jsm). Note that testing |require| is not safe in either -// situation, as it causes a ReferenceError. -var Ci, Cc, CC, Cu, Cr, Components; -if (this.require) { - ({ Ci, Cc, CC, Cu, Cr, components: Components }) = require("chrome"); -} else { - ({ - interfaces: Ci, - classes: Cc, - Constructor: CC, - utils: Cu, - results: Cr - }) = Components; -} - -// On B2G, if |this.require| is undefined at this point, it remains undefined -// later on when |DebuggerServer.registerModule| is called. On desktop (and -// perhaps other places), if |this.require| starts out undefined, it ends up -// being set to some native code by the time we get to |registerModule|. Here -// we perform a test early on, and then cache the correct require function for -// later use. -var localRequire; -if (this.require) { - localRequire = id => require(id); -} else { - let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); - localRequire = id => devtools.require(id); -} +// Until all Debugger server code is converted to SDK modules, +// imports Components.* alias from chrome module. +var { Ci, Cc, CC, Cu, Cr } = require("chrome"); +// On B2G, `this` != Global scope, so `Ci` won't be binded on `this` +// (i.e. this.Ci is undefined) Then later, when using loadSubScript, +// Ci,... won't be defined for sub scripts. +this.Ci = Ci; +this.Cc = Cc; +this.CC = CC; +this.Cu = Cu; +this.Cr = Cr; +// Overload `Components` to prevent SDK loader exception on Components +// object usage +Object.defineProperty(this, "Components", { + get: function () require("chrome").components +}); const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties"; @@ -68,10 +54,12 @@ function loadSubScript(aURL) } } -let loaderRequire = this.require; -this.require = null; -loadSubScript.call(this, "resource://gre/modules/commonjs/sdk/core/promise.js"); -this.require = loaderRequire; +let {defer, resolve, reject, promised, all} = require("sdk/core/promise"); +this.defer = defer; +this.resolve = resolve; +this.reject = reject; +this.promised = promised; +this.all = all; Cu.import("resource://gre/modules/devtools/SourceMap.jsm"); @@ -82,12 +70,14 @@ function dumpn(str) { dump("DBG-SERVER: " + str + "\n"); } } +this.dumpn = dumpn; function dbg_assert(cond, e) { if (!cond) { return e; } } +this.dbg_assert = dbg_assert; loadSubScript.call(this, "resource://gre/modules/devtools/server/transport.js"); @@ -324,7 +314,7 @@ var DebuggerServer = { } let moduleAPI = ModuleAPI(); - let mod = localRequire(id); + let mod = require(id); mod.register(moduleAPI); gRegisteredModules[id] = { module: mod, api: moduleAPI }; }, @@ -688,6 +678,8 @@ var DebuggerServer = { if (this.exports) { exports.DebuggerServer = DebuggerServer; } +// Needed on B2G (See header note) +this.DebuggerServer = DebuggerServer; /** * Construct an ActorPool. @@ -706,6 +698,8 @@ function ActorPool(aConnection) if (this.exports) { exports.ActorPool = ActorPool; } +// Needed on B2G (See header note) +this.ActorPool = ActorPool; ActorPool.prototype = { /** From 7bc8d071b62ffcc79a70da57298ce815973232fe Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Mon, 2 Dec 2013 12:02:29 -0500 Subject: [PATCH 47/67] Bug 854169 - Add missing moz.build file in tests subdir. r=Ms2ger --- toolkit/components/workerlz4/moz.build | 1 - toolkit/components/workerlz4/tests/moz.build | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 toolkit/components/workerlz4/tests/moz.build diff --git a/toolkit/components/workerlz4/moz.build b/toolkit/components/workerlz4/moz.build index 3ae13b1e73dc..6b8fc4d9baf0 100644 --- a/toolkit/components/workerlz4/moz.build +++ b/toolkit/components/workerlz4/moz.build @@ -16,7 +16,6 @@ SOURCES += [ ] TEST_DIRS += ['tests'] -XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] EXPORT_LIBRARY = True FINAL_LIBRARY = 'xul' diff --git a/toolkit/components/workerlz4/tests/moz.build b/toolkit/components/workerlz4/tests/moz.build new file mode 100644 index 000000000000..dcad37e80e1b --- /dev/null +++ b/toolkit/components/workerlz4/tests/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini'] From 64ea1cec79b572fefafe8b2ee43ed3a84a2c69e2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 2 Dec 2013 02:28:01 -0600 Subject: [PATCH 48/67] Bug 912057 - Replace Browser Debugger with Browser Toolbox. r=past --HG-- rename : browser/devtools/debugger/DebuggerProcess.jsm => browser/devtools/framework/ToolboxProcess.jsm --- browser/base/content/browser-menubar.inc | 6 +- browser/base/content/browser-sets.inc | 8 +- browser/base/content/browser.js | 6 +- browser/devtools/app-manager/content/index.js | 3 + browser/devtools/debugger/test/head.js | 8 +- browser/devtools/devtools-clhandler.js | 6 +- .../ToolboxProcess.jsm} | 34 +++--- browser/devtools/framework/gDevTools.jsm | 4 +- .../test/browser_dynamic_tool_enabling.js | 2 +- browser/devtools/framework/toolbox-hosts.js | 13 ++- .../framework/toolbox-process-window.js | 103 ++++++++++++++++++ .../framework/toolbox-process-window.xul | 41 +++++++ browser/devtools/jar.mn | 2 + browser/devtools/webconsole/hudservice.js | 25 ++++- .../locales/en-US/chrome/browser/browser.dtd | 8 +- toolkit/devtools/server/actors/inspector.js | 2 + toolkit/devtools/server/actors/root.js | 10 ++ toolkit/devtools/server/actors/styleeditor.js | 1 + 18 files changed, 233 insertions(+), 49 deletions(-) rename browser/devtools/{debugger/DebuggerProcess.jsm => framework/ToolboxProcess.jsm} (84%) create mode 100644 browser/devtools/framework/toolbox-process-window.js create mode 100644 browser/devtools/framework/toolbox-process-window.xul diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index 1db9222eb731..a5c301989edb 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -518,9 +518,9 @@ - + diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 52ce2d8792d7..ec1d6dbaff76 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -95,7 +95,7 @@