diff --git a/browser/base/content/browser-loop.js b/browser/base/content/browser-loop.js index 0f27072bad1..ac6b2ed8062 100644 --- a/browser/base/content/browser-loop.js +++ b/browser/base/content/browser-loop.js @@ -13,16 +13,50 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel (function() { LoopUI = { + /** + * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton + * instance for this window. + */ get toolbarButton() { delete this.toolbarButton; return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window); }, + /** + * @var {XULElement} panel Getter for the Loop panel element. + */ get panel() { delete this.panel; return this.panel = document.getElementById("loop-notification-panel"); }, + /** + * @var {XULElement|null} browser Getter for the Loop panel browser element. + * Will be NULL if the panel hasn't loaded yet. + */ + get browser() { + let browser = document.querySelector("#loop-notification-panel > #loop-panel-iframe"); + if (browser) { + delete this.browser; + this.browser = browser; + } + return browser; + }, + + /** + * @var {String|null} selectedTab Getter for the name of the currently selected + * tab inside the Loop panel. Will be NULL if + * the panel hasn't loaded yet. + */ + get selectedTab() { + if (!this.browser) { + return null; + } + + let selectedTab = this.browser.contentDocument.querySelector(".tab-view > .selected"); + return selectedTab && selectedTab.getAttribute("data-tab-name"); + }, + /** * @return {Promise} */ @@ -39,12 +73,24 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel }); }, + /** + * Toggle between opening or hiding the Loop panel. + * + * @param {DOMEvent} [event] Optional event that triggered the call to this + * function. + * @param {String} [tabId] Optional name of the tab to select after the panel + * has opened. Does nothing when the panel is hidden. + * @return {Promise} + */ togglePanel: function(event, tabId = null) { if (this.panel.state == "open") { - this.panel.hidePopup(); - } else { - this.openCallPanel(event, tabId); + return new Promise(resolve => { + this.panel.hidePopup(); + resolve(); + }); } + + return this.openCallPanel(event, tabId); }, /** diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/MozLoopAPI.jsm index a1a62faf692..b7fdb597e3a 100644 --- a/browser/components/loop/MozLoopAPI.jsm +++ b/browser/components/loop/MozLoopAPI.jsm @@ -738,13 +738,15 @@ function injectLoopAPI(targetWindow) { * Notifies the UITour module that an event occurred that it might be * interested in. * - * @param {String} subject Subject of the notification + * @param {String} subject Subject of the notification + * @param {mixed} [params] Optional parameters, providing more details to + * the notification subject */ notifyUITour: { enumerable: true, writable: true, - value: function(subject) { - UITour.notify(subject); + value: function(subject, params) { + UITour.notify(subject, params); } }, diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index 9eb82a55658..fdac7816b17 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -25,7 +25,8 @@ loop.panel = (function(_, mozL10n) { propTypes: { buttonsHidden: React.PropTypes.array, // The selectedTab prop is used by the UI showcase. - selectedTab: React.PropTypes.string + selectedTab: React.PropTypes.string, + mozLoop: React.PropTypes.object }, getDefaultProps: function() { @@ -34,6 +35,14 @@ loop.panel = (function(_, mozL10n) { }; }, + shouldComponentUpdate: function(nextProps, nextState) { + var tabChange = this.state.selectedTab !== nextState.selectedTab; + if (tabChange) { + this.props.mozLoop.notifyUITour("Loop:PanelTabChanged", nextState.selectedTab); + } + return tabChange; + }, + getInitialState: function() { // XXX Work around props.selectedTab being undefined initially. // When we don't need to rely on the pref, this can move back to @@ -799,7 +808,7 @@ loop.panel = (function(_, mozL10n) { React.createElement(NotificationListView, {notifications: this.props.notifications, clearOnDocumentHidden: true}), React.createElement(TabView, {ref: "tabView", selectedTab: this.props.selectedTab, - buttonsHidden: hideButtons}, + buttonsHidden: hideButtons, mozLoop: this.props.mozLoop}, React.createElement(Tab, {name: "rooms"}, React.createElement(RoomList, {dispatcher: this.props.dispatcher, store: this.props.roomStore, diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index 35ce9bb221c..2470573c284 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -25,7 +25,8 @@ loop.panel = (function(_, mozL10n) { propTypes: { buttonsHidden: React.PropTypes.array, // The selectedTab prop is used by the UI showcase. - selectedTab: React.PropTypes.string + selectedTab: React.PropTypes.string, + mozLoop: React.PropTypes.object }, getDefaultProps: function() { @@ -34,6 +35,14 @@ loop.panel = (function(_, mozL10n) { }; }, + shouldComponentUpdate: function(nextProps, nextState) { + var tabChange = this.state.selectedTab !== nextState.selectedTab; + if (tabChange) { + this.props.mozLoop.notifyUITour("Loop:PanelTabChanged", nextState.selectedTab); + } + return tabChange; + }, + getInitialState: function() { // XXX Work around props.selectedTab being undefined initially. // When we don't need to rely on the pref, this can move back to @@ -799,7 +808,7 @@ loop.panel = (function(_, mozL10n) { + buttonsHidden={hideButtons} mozLoop={this.props.mozLoop}> - + + + + + + + + + @@ -163,4 +180,6 @@ use[id$="-red"] { + + diff --git a/browser/components/loop/content/shared/js/views.js b/browser/components/loop/content/shared/js/views.js index 9f090853f2b..2b23fb2bc74 100644 --- a/browser/components/loop/content/shared/js/views.js +++ b/browser/components/loop/content/shared/js/views.js @@ -355,7 +355,7 @@ loop.shared.views = (function(_, l10n) { /* jshint ignore:start */ return ( React.createElement("div", {className: "video-layout-wrapper"}, - React.createElement("div", {className: "conversation"}, + React.createElement("div", {className: "conversation in-call"}, React.createElement("div", {className: "media nested"}, React.createElement("div", {className: "video_wrapper remote_wrapper"}, React.createElement("div", {className: "video_inner remote remote-stream"}) diff --git a/browser/components/loop/content/shared/js/views.jsx b/browser/components/loop/content/shared/js/views.jsx index 936575e755c..14683314185 100644 --- a/browser/components/loop/content/shared/js/views.jsx +++ b/browser/components/loop/content/shared/js/views.jsx @@ -355,7 +355,7 @@ loop.shared.views = (function(_, l10n) { /* jshint ignore:start */ return (
-
+
diff --git a/browser/components/loop/test/desktop-local/panel_test.js b/browser/components/loop/test/desktop-local/panel_test.js index 82a14639601..ff967187a80 100644 --- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -60,7 +60,8 @@ describe("loop.panel", function() { }, on: sandbox.stub() }, - confirm: sandbox.stub() + confirm: sandbox.stub(), + notifyUITour: sandbox.stub() }; document.mozL10n.initialize(navigator.mozLoop); diff --git a/browser/components/loop/test/mochitest/browser_toolbarbutton.js b/browser/components/loop/test/mochitest/browser_toolbarbutton.js index a28d2b1e695..f5dc1a9cd26 100644 --- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js +++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js @@ -11,6 +11,17 @@ Components.utils.import("resource://gre/modules/Promise.jsm", this); const {LoopRoomsInternal} = Components.utils.import("resource:///modules/loop/LoopRooms.jsm", {}); Services.prefs.setBoolPref("loop.gettingStarted.seen", true); +const fxASampleToken = { + token_type: "bearer", + access_token: "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752", + scope: "profile" +}; + +const fxASampleProfile = { + email: "test@example.com", + uid: "abcd1234" +}; + registerCleanupFunction(function*() { MozLoopService.doNotDisturb = false; MozLoopServiceInternal.fxAOAuthProfile = null; @@ -18,6 +29,42 @@ registerCleanupFunction(function*() { Services.prefs.clearUserPref("loop.gettingStarted.seen"); }); +add_task(function* test_LoopUI_getters() { + Assert.ok(LoopUI.panel, "LoopUI panel element should be set"); + Assert.strictEqual(LoopUI.browser, null, "Browser element should not be there yet"); + Assert.strictEqual(LoopUI.selectedTab, null, "No tab should be selected yet"); + + // Load and show the Loop panel for the very first time this session. + yield loadLoopPanel(); + Assert.ok(LoopUI.browser, "Browser element should be there"); + Assert.strictEqual(LoopUI.selectedTab, "rooms", "Initially the rooms tab should be selected"); + + // Hide the panel. + yield LoopUI.togglePanel(); + Assert.strictEqual(LoopUI.selectedTab, "rooms", "Rooms tab should still be selected"); + + // Make sure the contacts tab shows up by simulating a login. + MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken; + MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile; + yield MozLoopServiceInternal.notifyStatusChanged("login"); + + // Programmatically select the contacts tab. + yield LoopUI.togglePanel(null, "contacts"); + Assert.strictEqual(LoopUI.selectedTab, "contacts", "Contacts tab should be selected now"); + + // Switch back to the rooms tab. + yield LoopUI.openCallPanel(null, "rooms"); + Assert.strictEqual(LoopUI.selectedTab, "rooms", "Rooms tab should be selected now"); + + // Hide the panel. + yield LoopUI.togglePanel(); + + // Logout to prevent interfering with the tests after this one. + MozLoopServiceInternal.fxAOAuthTokenData = + MozLoopServiceInternal.fxAOAuthProfile = null; + yield MozLoopServiceInternal.notifyStatusChanged(); +}); + add_task(function* test_doNotDisturb() { Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state"); yield MozLoopService.doNotDisturb = true; @@ -30,8 +77,8 @@ add_task(function* test_doNotDisturb_with_login() { Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state"); yield MozLoopService.doNotDisturb = true; Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state"); - MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"}; - MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"}; + MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken; + MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile; yield MozLoopServiceInternal.notifyStatusChanged("login"); Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state"); yield loadLoopPanel(); @@ -56,7 +103,7 @@ add_task(function* test_error_with_login() { Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state"); yield MozLoopServiceInternal.setError("testing", {}); Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state"); - MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"}; + MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile; MozLoopServiceInternal.notifyStatusChanged("login"); Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state"); yield MozLoopServiceInternal.clearError("testing"); @@ -68,8 +115,8 @@ add_task(function* test_error_with_login() { add_task(function* test_active() { Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state"); - MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"}; - MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"}; + MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken; + MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile; yield MozLoopServiceInternal.notifyStatusChanged("login"); Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state"); yield loadLoopPanel(); diff --git a/browser/components/preferences/in-content/tests/browser_bug410900.js b/browser/components/preferences/in-content/tests/browser_bug410900.js index 5b100966dfd..b445d1d1655 100644 --- a/browser/components/preferences/in-content/tests/browser_bug410900.js +++ b/browser/components/preferences/in-content/tests/browser_bug410900.js @@ -6,6 +6,8 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm"); function test() { waitForExplicitFinish(); + Services.prefs.setBoolPref("browser.preferences.inContent", true); + registerCleanupFunction(() => Services.prefs.clearUserPref("browser.preferences.inContent")); // Setup a phony handler to ensure the app pane will be populated. var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. diff --git a/browser/components/preferences/in-content/tests/browser_change_app_handler.js b/browser/components/preferences/in-content/tests/browser_change_app_handler.js index ffd556316cb..43781bb87b9 100644 --- a/browser/components/preferences/in-content/tests/browser_change_app_handler.js +++ b/browser/components/preferences/in-content/tests/browser_change_app_handler.js @@ -30,7 +30,9 @@ add_task(function*() { let chooseItem = list.firstChild.querySelector(".choose-app-item"); let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul"); - chooseItem.click(); + let cmdEvent = win.document.createEvent("xulcommandevent"); + cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null); + chooseItem.dispatchEvent(cmdEvent); let dialog = yield dialogLoadedPromise; info("Dialog loaded"); @@ -57,7 +59,9 @@ add_task(function*() { dialogLoadedPromise = promiseLoadSubDialog("chrome://browser/content/preferences/applicationManager.xul"); let manageItem = list.firstChild.querySelector(".manage-app-item"); - manageItem.click(); + cmdEvent = win.document.createEvent("xulcommandevent"); + cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null); + manageItem.dispatchEvent(cmdEvent); dialog = yield dialogLoadedPromise; info("Dialog loaded the second time"); diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index 270981defcd..6a3499a64e4 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -139,23 +139,23 @@ this.UITour = { ["loop-newRoom", { infoPanelPosition: "leftcenter topright", query: (aDocument) => { - let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop-panel-iframe"); - if (!loopBrowser) { + let loopUI = aDocument.defaultView.LoopUI; + if (loopUI.selectedTab != "rooms") { return null; } // Use the parentElement full-width container of the button so our arrow // doesn't overlap the panel contents much. - return loopBrowser.contentDocument.querySelector(".new-room-button").parentElement; + return loopUI.browser.contentDocument.querySelector(".new-room-button").parentElement; }, }], ["loop-roomList", { infoPanelPosition: "leftcenter topright", query: (aDocument) => { - let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop-panel-iframe"); - if (!loopBrowser) { + let loopUI = aDocument.defaultView.LoopUI; + if (loopUI.selectedTab != "rooms") { return null; } - return loopBrowser.contentDocument.querySelector(".room-list"); + return loopUI.browser.contentDocument.querySelector(".room-list"); }, }], ["loop-selectedRoomButtons", { @@ -178,7 +178,7 @@ this.UITour = { }], ["loop-signInUpLink", { query: (aDocument) => { - let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop-panel-iframe"); + let loopBrowser = aDocument.defaultView.LoopUI.browser; if (!loopBrowser) { return null; } diff --git a/browser/components/uitour/test/browser_UITour_loop.js b/browser/components/uitour/test/browser_UITour_loop.js index 88c842211d0..64bfc69298b 100644 --- a/browser/components/uitour/test/browser_UITour_loop.js +++ b/browser/components/uitour/test/browser_UITour_loop.js @@ -11,6 +11,7 @@ let loopPanel = document.getElementById("loop-notification-panel"); Components.utils.import("resource:///modules/UITour.jsm"); const { LoopRooms } = Components.utils.import("resource:///modules/loop/LoopRooms.jsm", {}); +const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); function test() { UITourTest(); @@ -181,6 +182,53 @@ let tests = [ }); }); }, + taskify(function* test_panelTabChangeNotifications() { + // First make sure the Loop panel looks like we're logged in to have more than + // just one tab to switch to. + const fxASampleToken = { + token_type: "bearer", + access_token: "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752", + scope: "profile" + }; + const fxASampleProfile = { + email: "test@example.com", + uid: "abcd1234" + }; + MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken; + MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile; + yield MozLoopServiceInternal.notifyStatusChanged("login"); + + // Show the Loop menu. + yield showMenuPromise("loop"); + + // Listen for and test the notifications that will arrive from now on. + let tabChangePromise = new Promise(resolve => { + gContentAPI.observe((event, params) => { + is(event, "Loop:PanelTabChanged", "Check Loop:PanelTabChanged notification"); + is(params, "contacts", "Check the tab name param"); + + gContentAPI.observe((event, params) => { + is(event, "Loop:PanelTabChanged", "Check Loop:PanelTabChanged notification"); + is(params, "rooms", "Check the tab name param"); + + gContentAPI.observe((event, params) => { + ok(false, "No more notifications should have arrived"); + }); + resolve(); + }); + }); + }); + + // Switch to the contacts tab. + yield window.LoopUI.openCallPanel(null, "contacts"); + + // Logout. The panel tab will switch back to 'rooms'. + MozLoopServiceInternal.fxAOAuthTokenData = + MozLoopServiceInternal.fxAOAuthProfile = null; + yield MozLoopServiceInternal.notifyStatusChanged(); + + yield tabChangePromise; + }), runOffline(function test_notifyLoopChatWindowOpenedClosed(done) { gContentAPI.observe((event, params) => { is(event, "Loop:ChatWindowOpened", "Check Loop:ChatWindowOpened notification"); diff --git a/browser/devtools/netmonitor/test/browser_net_filter-01.js b/browser/devtools/netmonitor/test/browser_net_filter-01.js index 4186baad191..2d129c483b1 100644 --- a/browser/devtools/netmonitor/test/browser_net_filter-01.js +++ b/browser/devtools/netmonitor/test/browser_net_filter-01.js @@ -4,6 +4,22 @@ /** * Test if filtering items in the network table works correctly. */ +const BASIC_REQUESTS = [ + { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" }, + { url: "sjs_content-type-test-server.sjs?fmt=css" }, + { url: "sjs_content-type-test-server.sjs?fmt=js" }, +]; + +const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=font" }, + { url: "sjs_content-type-test-server.sjs?fmt=image" }, + { url: "sjs_content-type-test-server.sjs?fmt=audio" }, + { url: "sjs_content-type-test-server.sjs?fmt=video" }, +]); + +const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=flash" }, +]); function test() { initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => { @@ -207,6 +223,7 @@ function test() { return promise.resolve(null); } - aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }'); + loadCommonFrameScript(); + performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH); }); } diff --git a/browser/devtools/netmonitor/test/browser_net_filter-02.js b/browser/devtools/netmonitor/test/browser_net_filter-02.js index 2ab70bee08b..aafa039d214 100644 --- a/browser/devtools/netmonitor/test/browser_net_filter-02.js +++ b/browser/devtools/netmonitor/test/browser_net_filter-02.js @@ -4,6 +4,22 @@ /** * Test if filtering items in the network table works correctly with new requests. */ +const BASIC_REQUESTS = [ + { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" }, + { url: "sjs_content-type-test-server.sjs?fmt=css" }, + { url: "sjs_content-type-test-server.sjs?fmt=js" }, +]; + +const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=font" }, + { url: "sjs_content-type-test-server.sjs?fmt=image" }, + { url: "sjs_content-type-test-server.sjs?fmt=audio" }, + { url: "sjs_content-type-test-server.sjs?fmt=video" }, +]); + +const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=flash" }, +]); function test() { initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => { @@ -37,7 +53,7 @@ function test() { }) .then(() => { info("Performing more requests."); - aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }'); + performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH); return waitForNetworkEvents(aMonitor, 8); }) .then(() => { @@ -47,7 +63,7 @@ function test() { }) .then(() => { info("Performing more requests."); - aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }'); + performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH); return waitForNetworkEvents(aMonitor, 8); }) .then(() => { @@ -169,6 +185,7 @@ function test() { return promise.resolve(null); } - aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }'); + loadCommonFrameScript(); + performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH); }); } diff --git a/browser/devtools/netmonitor/test/browser_net_filter-03.js b/browser/devtools/netmonitor/test/browser_net_filter-03.js index fc177271dc6..8e595b69bcd 100644 --- a/browser/devtools/netmonitor/test/browser_net_filter-03.js +++ b/browser/devtools/netmonitor/test/browser_net_filter-03.js @@ -5,6 +5,22 @@ * Test if filtering items in the network table works correctly with new requests * and while sorting is enabled. */ +const BASIC_REQUESTS = [ + { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" }, + { url: "sjs_content-type-test-server.sjs?fmt=css" }, + { url: "sjs_content-type-test-server.sjs?fmt=js" }, +]; + +const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=font" }, + { url: "sjs_content-type-test-server.sjs?fmt=image" }, + { url: "sjs_content-type-test-server.sjs?fmt=audio" }, + { url: "sjs_content-type-test-server.sjs?fmt=video" }, +]); + +const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=flash" }, +]); function test() { initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => { @@ -44,7 +60,7 @@ function test() { }) .then(() => { info("Performing more requests."); - aDebuggee.performRequests('{ "getMedia": true }'); + performRequestsInContent(REQUESTS_WITH_MEDIA); return waitForNetworkEvents(aMonitor, 7); }) .then(() => { @@ -55,7 +71,7 @@ function test() { }) .then(() => { info("Performing more requests."); - aDebuggee.performRequests('{ "getMedia": true }'); + performRequestsInContent(REQUESTS_WITH_MEDIA); return waitForNetworkEvents(aMonitor, 7); }) .then(() => { @@ -167,7 +183,15 @@ function test() { return promise.resolve(null); } - let str = "'

'" + new Array(10).join(Math.random(10)) + "'

'"; - aDebuggee.performRequests('{ "htmlContent": "' + str + '", "getMedia": true }'); + // The test assumes that the first HTML request here has a longer response + // body than the other HTML requests performed later during the test. + let requests = Cu.cloneInto(REQUESTS_WITH_MEDIA, {}); + + let newres = "res=

" + new Array(10).join(Math.random(10)) + "

"; + requests[0].url = requests[0].url.replace("res=undefined", newres); + + loadCommonFrameScript(); + performRequestsInContent(requests); + }); } diff --git a/browser/devtools/netmonitor/test/browser_net_filter-04.js b/browser/devtools/netmonitor/test/browser_net_filter-04.js index 1840aec6467..64f6e58f550 100644 --- a/browser/devtools/netmonitor/test/browser_net_filter-04.js +++ b/browser/devtools/netmonitor/test/browser_net_filter-04.js @@ -5,6 +5,23 @@ * Tests if invalid filter types are sanitized when loaded from the preferences. */ +const BASIC_REQUESTS = [ + { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" }, + { url: "sjs_content-type-test-server.sjs?fmt=css" }, + { url: "sjs_content-type-test-server.sjs?fmt=js" }, +]; + +const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=font" }, + { url: "sjs_content-type-test-server.sjs?fmt=image" }, + { url: "sjs_content-type-test-server.sjs?fmt=audio" }, + { url: "sjs_content-type-test-server.sjs?fmt=video" }, +]); + +const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([ + { url: "sjs_content-type-test-server.sjs?fmt=flash" }, +]); + function test() { Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]'); @@ -36,6 +53,7 @@ function test() { }); }); - aDebuggee.performRequests('{ "getMedia": true, "getFlash": true }'); + loadCommonFrameScript(); + performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH); }); } diff --git a/browser/devtools/netmonitor/test/head.js b/browser/devtools/netmonitor/test/head.js index 561ae17aeb6..b2145be6cd7 100644 --- a/browser/devtools/netmonitor/test/head.js +++ b/browser/devtools/netmonitor/test/head.js @@ -49,6 +49,8 @@ const CORS_SJS_PATH = "/browser/browser/devtools/netmonitor/test/sjs_cors-test-s const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; +const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js" + gDevTools.testing = true; SimpleTest.registerCleanupFunction(() => { gDevTools.testing = false; @@ -416,3 +418,77 @@ function testFilterButtonsCustom(aMonitor, aIsChecked) { } } } + +/** + * Loads shared/frame-script-utils.js in the specified tab. + * + * @param tab + * Optional tab to load the frame script in. Defaults to the current tab. + */ +function loadCommonFrameScript(tab) { + let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser; + + browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false); +} + +/** + * Perform the specified requests in the context of the page content. + * + * @param Array requests + * An array of objects specifying the requests to perform. See + * shared/frame-script-utils.js for more information. + * + * @return A promise that resolves once the requests complete. + */ +function performRequestsInContent(requests) { + info("Performing requests in the context of the content."); + return executeInContent("devtools:test:xhr", requests) +} + +/** + * Send an async message to the frame script (chrome -> content) and wait for a + * response message with the same name (content -> chrome). + * + * @param String name + * The message name. Should be one of the messages defined + * shared/frame-script-utils.js + * @param Object data + * Optional data to send along + * @param Object objects + * Optional CPOW objects to send along + * @param Boolean expectResponse + * If set to false, don't wait for a response with the same name from the + * content script. Defaults to true. + * + * @return Promise + * Resolves to the response data if a response is expected, immediately + * resolves otherwise + */ +function executeInContent(name, data={}, objects={}, expectResponse=true) { + let mm = gBrowser.selectedBrowser.messageManager; + + mm.sendAsyncMessage(name, data, objects); + if (expectResponse) { + return waitForContentMessage(name); + } else { + return promise.resolve(); + } +} + +/** + * Wait for a content -> chrome message on the message manager (the window + * messagemanager is used). + * @param {String} name The message name + * @return {Promise} A promise that resolves to the response data when the + * message has been received + */ +function waitForContentMessage(name) { + let mm = gBrowser.selectedBrowser.messageManager; + + let def = promise.defer(); + mm.addMessageListener(name, function onMessage(msg) { + mm.removeMessageListener(name, onMessage); + def.resolve(msg); + }); + return def.promise; +} diff --git a/browser/devtools/shared/frame-script-utils.js b/browser/devtools/shared/frame-script-utils.js index 5188526ffe1..a2236a715d8 100644 --- a/browser/devtools/shared/frame-script-utils.js +++ b/browser/devtools/shared/frame-script-utils.js @@ -3,6 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +const Cu = Components.utils; + +const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +devtools.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise"); +devtools.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm", "Task"); addMessageListener("devtools:test:history", function ({ data }) { content.history[data.direction](); @@ -22,6 +27,79 @@ addMessageListener("devtools:test:console", function ({ data }) { content.console[method].apply(content.console, data); }); +/** + * Performs a single XMLHttpRequest and returns a promise that resolves once + * the request has loaded. + * + * @param Object data + * { method: the request method (default: "GET"), + * url: the url to request (default: content.location.href), + * body: the request body to send (default: ""), + * nocache: append an unique token to the query string (default: true) + * } + * + * @return Promise A promise that's resolved with object + * { status: XMLHttpRequest.status, + * response: XMLHttpRequest.response } + * + */ +function promiseXHR(data) { + let xhr = new content.XMLHttpRequest(); + + let method = data.method || "GET"; + let url = data.url || content.location.href; + let body = data.body || ""; + + if (data.nocache) { + url += "?devtools-cachebust=" + Math.random(); + } + + let deferred = promise.defer(); + xhr.addEventListener("loadend", function loadend(event) { + xhr.removeEventListener("loadend", loadend); + deferred.resolve({ status: xhr.status, response: xhr.response }); + }); + + xhr.open(method, url); + xhr.send(body); + return deferred.promise; + +} + +/** + * Performs XMLHttpRequest request(s) in the context of the page. The data + * parameter can be either a single object or an array of objects described below. + * The requests will be performed one at a time in the order they appear in the data. + * + * The objects should have following form (any of them can be omitted; defaults + * shown below): + * { + * method: "GET", + * url: content.location.href, + * body: "", + * nocache: true, // Adds a cache busting random token to the URL + * } + * + * The handler will respond with devtools:test:xhr message after all requests + * have finished. Following data will be available for each requests + * (in the same order as requests): + * { + * status: XMLHttpRequest.status + * response: XMLHttpRequest.response + * } + */ +addMessageListener("devtools:test:xhr", Task.async(function* ({ data }) { + let requests = Array.isArray(data) ? data : [data]; + let responses = []; + + for (let request of requests) { + let response = yield promiseXHR(request); + responses.push(response); + } + + sendAsyncMessage("devtools:test:xhr", responses); +})); + // To eval in content, look at `evalInDebuggee` in the head.js of canvasdebugger // for an example. addMessageListener("devtools:test:eval", function ({ data }) { diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 0d4e91df2a1..a5c6871fca0 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -2579,6 +2579,16 @@ EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent, for (nsIFrame* current = targetFrame; current; current = nsLayoutUtils::GetCrossDocParentFrame(current)) { + // e10s - mark remote content as pannable. This is a work around since + // we don't have access to remote frame scroll info here. Apz data may + // assist is solving this. + if (current && IsRemoteTarget(current->GetContent())) { + panDirection = WidgetGestureNotifyEvent::ePanBoth; + // We don't know when we reach bounds, so just disable feedback for now. + displayPanFeedback = false; + break; + } + nsIAtom* currentFrameType = current->GetType(); // Scrollbars should always be draggable diff --git a/mobile/android/base/reading/ReadingListSyncAdapter.java b/mobile/android/base/reading/ReadingListSyncAdapter.java index 7b3474294e6..4033b7d0257 100644 --- a/mobile/android/base/reading/ReadingListSyncAdapter.java +++ b/mobile/android/base/reading/ReadingListSyncAdapter.java @@ -1,5 +1,4 @@ -/* -*- Mode: Java; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public +/* 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/. */ diff --git a/mobile/android/base/resources/values-v21/themes.xml b/mobile/android/base/resources/values-v21/themes.xml new file mode 100644 index 00000000000..41eef5700ba --- /dev/null +++ b/mobile/android/base/resources/values-v21/themes.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/mobile/android/base/resources/values/colors.xml b/mobile/android/base/resources/values/colors.xml index ec56d47d7ab..a0e28fffd70 100644 --- a/mobile/android/base/resources/values/colors.xml +++ b/mobile/android/base/resources/values/colors.xml @@ -4,6 +4,8 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + #363B40 + #FFF5F5F5 diff --git a/mobile/android/base/sync/setup/SyncAccounts.java b/mobile/android/base/sync/setup/SyncAccounts.java index 4f15beabf45..b7a8f2b0280 100644 --- a/mobile/android/base/sync/setup/SyncAccounts.java +++ b/mobile/android/base/sync/setup/SyncAccounts.java @@ -285,7 +285,7 @@ public class SyncAccounts { Logger.debug(LOG_TAG, "Account " + account + " added successfully."); setSyncAutomatically(account, syncAutomatically); - setIsSyncable(account, syncAutomatically); + setIsSyncable(account, true); Logger.debug(LOG_TAG, "Set account to sync automatically? " + syncAutomatically + "."); try { diff --git a/mobile/android/base/widget/DoorHanger.java b/mobile/android/base/widget/DoorHanger.java index afbd32a5e71..b53e2f51aaa 100644 --- a/mobile/android/base/widget/DoorHanger.java +++ b/mobile/android/base/widget/DoorHanger.java @@ -17,6 +17,8 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.text.SpannableString; +import android.text.Spanned; +import android.text.Html; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; @@ -160,7 +162,9 @@ public class DoorHanger extends LinearLayout { } public void setMessage(String message) { - mTextView.setText(message); + Spanned markupMessage = Html.fromHtml(message); + mTextView.setMovementMethod(LinkMovementMethod.getInstance()); // Necessary for clickable links + mTextView.setText(markupMessage); } public void setIcon(int resId) { diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm index 8cebc55d000..06feb7a16dd 100644 --- a/netwerk/base/NetUtil.jsm +++ b/netwerk/base/NetUtil.jsm @@ -22,6 +22,9 @@ const Cu = Components.utils; const PR_UINT32_MAX = 0xffffffff; +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + //////////////////////////////////////////////////////////////////////////////// //// NetUtil Object @@ -81,22 +84,24 @@ this.NetUtil = { }, /** - * Asynchronously opens a source and fetches the response. A source can be - * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream. The - * provided callback will get an input stream containing the response, the - * result code, and a reference to the request. + * Asynchronously opens a source and fetches the response. While the fetch + * is asynchronous, I/O may happen on the main thread. When reading from + * a local file, prefer using "OS.File" methods instead. * * @param aSource - * The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream - * to open. + * This argument can be one of the following: + * - An options object that will be passed to NetUtil.newChannel. + * - An existing nsIChannel. + * - An existing nsIInputStream. + * Using an nsIURI, nsIFile, or string spec directly is deprecated. * @param aCallback * The callback function that will be notified upon completion. It - * will get two arguments: + * will get these arguments: * 1) An nsIInputStream containing the data from aSource, if any. * 2) The status code from opening the source. * 3) Reference to the nsIRequest. */ - asyncFetch: function NetUtil_asyncOpen(aSource, aCallback) + asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) { if (!aSource || !aCallback) { let exception = new Components.Exception( @@ -154,31 +159,7 @@ this.NetUtil = { }, /** - * Asynchronously opens a source and fetches the response. A source can be - * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream. The - * provided callback will get an input stream containing the response, the - * result code, and a reference to the request. - * - * Please note, if aSource is an instance of an nsIChannel, then - * aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aSecurityFlags, - * aContentPolicyType must be "undefined". - * - * @param aSource - * The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream - * to open. - * @param aCallback - * The callback function that will be notified upon completion. It - * will get two arguments: - * 1) An nsIInputStream containing the data from aSource, if any. - * 2) The status code from opening the source. - * 3) Reference to the nsIRequest. - * @param aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal - * aSecurityFlags, aContentPolicyType - * See param description in NetUtil_newChannel2. - * - * Note: As an interim we have asyncFetch as well as asyncFetch2. - * Once Bug 1087720 (which converts all js callers to use - * asyncFetch2) lands, we can remove asyncFetch completely. + * @deprecated Use asyncFecth({ ...options... }, callback) instead. */ asyncFetch2: function NetUtil_asyncFetch2(aSource, aCallback, @@ -303,106 +284,194 @@ this.NetUtil = { }, /** - * Constructs a new channel for the given spec, character set, and base URI, - * or nsIURI, or nsIFile. + * Constructs a new channel for the given source. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. * * @param aWhatToLoad - * The string spec for the desired URI, an nsIURI, or an nsIFile. - * @param aOriginCharset [optional] + * This argument used to be a string spec for the desired URI, an + * nsIURI, or an nsIFile. Now it should be an options object with + * the following properties: + * { + * uri: + * The full URI spec string or nsIURI to create the channel for. + * Note that this cannot be an nsIFile and you cannot specify a + * non-default charset or base URI. Call NetUtil.newURI first if + * you need to construct an URI using those options. + * loadingNode: + * The loadingDocument of the channel. + * The element or document where the result of this request will + * be used. This is the document/element that will get access to + * the result of this request. For example for an image load, + * it's the document in which the image will be loaded. And for + * a CSS stylesheet it's the document whose rendering will be + * affected by the stylesheet. + * If possible, pass in the element which is performing the load. + * But if the load is coming from a JS API (such as + * XMLHttpRequest) or if the load might be coalesced across + * multiple elements (such as for ) then pass in the + * Document node instead. + * For loads that are not related to any document, such as loads + * coming from addons or internal browser features, omit this + * property and specify a loadingPrincipal or + * loadUsingSystemPrincipal instead. + * loadingPrincipal: + * The loadingPrincipal of the channel. + * The principal of the document where the result of this request + * will be used. + * This is generally the principal of the loadingNode. However + * for loads where loadingNode is omitted this argument still + * needs to be passed. For example for loads from a WebWorker, + * pass the principal of that worker. For loads from an addon or + * from internal browser features, pass the system principal. + * This principal should almost always be the system principal if + * loadingNode is omitted, in which case you can use the + * useSystemPrincipal property. The only exception to this is + * for loads from WebWorkers since they don't have any nodes to + * be passed as loadingNode. + * Please note, loadingPrincipal is *not* the principal of the + * resource being loaded, but rather the principal of the context + * where the resource will be used. + * loadUsingSystemPrincipal: + * Set this to true to use the system principal as + * loadingPrincipal. This must be omitted if loadingPrincipal or + * loadingNode are present. + * This should be used with care as it skips security checks. + * triggeringPrincipal: + * The triggeringPrincipal of the load. + * The triggeringPrincipal is the principal of the resource that + * caused this particular URL to be loaded. + * Most likely the triggeringPrincipal and the loadingPrincipal + * are identical, in which case the triggeringPrincipal can be + * left out. In some cases the loadingPrincipal and the + * triggeringPrincipal are different however, e.g. a stylesheet + * may import a subresource. In that case the principal of the + * stylesheet which contains the import command is the + * triggeringPrincipal, and the principal of the document whose + * rendering is affected is the loadingPrincipal. + * securityFlags: + * The securityFlags of the channel. + * Any of the securityflags defined in nsILoadInfo.idl. + * contentPolicyType: + * The contentPolicyType of the channel. + * Any of the content types defined in nsIContentPolicy.idl. + * } + * @param aOriginCharset [deprecated] * The character set for the URI. Only used if aWhatToLoad is a - * string. - * @param aBaseURI [optional] - * The base URI for the spec. Only used if aWhatToLoad is a string. + * string, which is a deprecated API. Must be undefined otherwise. + * Use NetUtil.newURI if you need to use this option. + * @param aBaseURI [deprecated] + * The base URI for the spec. Only used if aWhatToLoad is a string, + * which is a deprecated API. Must be undefined otherwise. Use + * NetUtil.newURI if you need to use this option. * * @return an nsIChannel object. */ newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI) { - if (!aWhatToLoad) { - let exception = new Components.Exception( - "Must have a non-null string spec, nsIURI, or nsIFile object", + // Check for the deprecated API first. + if (typeof aWhatToLoad == "string" || + (aWhatToLoad instanceof Ci.nsIFile) || + (aWhatToLoad instanceof Ci.nsIURI)) { + + let uri = (aWhatToLoad instanceof Ci.nsIURI) + ? aWhatToLoad + : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); + + return this.ioService.newChannelFromURI(uri); + } + + // We are using the updated API, that requires only the options object. + if (typeof aWhatToLoad != "object" || + aOriginCharset !== undefined || + aBaseURI !== undefined) { + + throw new Components.Exception( + "newChannel requires a single object argument", Cr.NS_ERROR_INVALID_ARG, Components.stack.caller ); - throw exception; } - let uri = aWhatToLoad; - if (!(aWhatToLoad instanceof Ci.nsIURI)) { - // We either have a string or an nsIFile that we'll need a URI for. - uri = this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); + let { uri, + loadingNode, + loadingPrincipal, + loadUsingSystemPrincipal, + triggeringPrincipal, + securityFlags, + contentPolicyType } = aWhatToLoad; + + if (!uri) { + throw new Components.Exception( + "newChannel requires the 'uri' property on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); } - return this.ioService.newChannelFromURI(uri); + if (typeof uri == "string") { + uri = this.newURI(uri); + } + + if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires at least one of the 'loadingNode'," + + " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" + + " properties on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (loadUsingSystemPrincipal === true) { + if (loadingNode || loadingPrincipal) { + throw new Components.Exception( + "newChannel does not accept 'loadUsingSystemPrincipal'" + + " if the 'loadingNode' or 'loadingPrincipal' properties" + + " are present on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + loadingPrincipal = Services.scriptSecurityManager + .getSystemPrincipal(); + } else if (loadUsingSystemPrincipal !== undefined) { + throw new Components.Exception( + "newChannel requires the 'loadUsingSystemPrincipal'" + + " property on the options object to be 'true' or 'undefined'.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (securityFlags === undefined) { + securityFlags = Ci.nsILoadInfo.SEC_NORMAL; + } + + if (contentPolicyType === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'contentPolicyType' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; + } + + return this.ioService.newChannelFromURI2(uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType); }, /** - * Constructs a new channel for the given spec, character set, and base URI, - * or nsIURI, or nsIFile. - * - * @param aWhatToLoad - * The string spec for the desired URI, an nsIURI, or an nsIFile. - * @param aOriginCharset - * The character set for the URI. Only used if aWhatToLoad is a - * string. - * @param aBaseURI - * The base URI for the spec. Only used if aWhatToLoad is a string. - * @param aLoadingNode - * The loadingDocument of the channel. - * The element or document where the result of this request will be - * used. This is the document/element that will get access to the - * result of this request. For example for an image load, it's the - * document in which the image will be loaded. And for a CSS - * stylesheet it's the document whose rendering will be affected by - * the stylesheet. - * If possible, pass in the element which is performing the load. But - * if the load is coming from a JS API (such as XMLHttpRequest) or if - * the load might be coalesced across multiple elements (such as - * for ) then pass in the Document node instead. - * For loads that are not related to any document, such as loads coming - * from addons or internal browser features, use null here. - * @param aLoadingPrincipal - * The loadingPrincipal of the channel. - * The principal of the document where the result of this request will - * be used. - * This is generally the principal of the aLoadingNode. However for - * loads where aLoadingNode is null this argument still needs to be - * passed. For example for loads from a WebWorker, pass the principal - * of that worker. For loads from an addon or from internal browser - * features, pass the system principal. - * This principal should almost always be the system principal if - * aLoadingNode is null. The only exception to this is for loads - * from WebWorkers since they don't have any nodes to be passed as - * aLoadingNode. - * Please note, aLoadingPrincipal is *not* the principal of the - * resource being loaded. But rather the principal of the context - * where the resource will be used. - * @param aTriggeringPrincipal - * The triggeringPrincipal of the load. - * The triggeringPrincipal is the principal of the resource that caused - * this particular URL to be loaded. - * Most likely the triggeringPrincipal and the loadingPrincipal are - * identical, in which case the triggeringPrincipal can be left out. - * In some cases the loadingPrincipal and the triggeringPrincipal are - * different however, e.g. a stylesheet may import a subresource. In - * that case the principal of the stylesheet which contains the - * import command is the triggeringPrincipal, and the principal of - * the document whose rendering is affected is the loadingPrincipal. - * @param aSecurityFlags - * The securityFlags of the channel. - * Any of the securityflags defined in nsILoadInfo.idl - * @param aContentPolicyType - * The contentPolicyType of the channel. - * Any of the content types defined in nsIContentPolicy.idl - * @return an nsIChannel object. - * - * Keep in mind that URIs coming from a webpage should *never* use the - * systemPrincipal as the loadingPrincipal. - * - * Note: As an interim we have newChannel as well as newChannel2. - * Once Bug 1087720 (which converts all js callers to use - * newChannel2) lands, we can remove newChannel completely. + * @deprecated Use newChannel({ ...options... }) instead. */ newChannel2: function NetUtil_newChannel2(aWhatToLoad, aOriginCharset, @@ -485,7 +554,7 @@ this.NetUtil = { let cis = Cc["@mozilla.org/intl/converter-input-stream;1"]. createInstance(Ci.nsIConverterInputStream); try { - // When replacement is set, the character that is unknown sequence + // When replacement is set, the character that is unknown sequence // replaces with aOptions.replacement character. if (!("replacement" in aOptions)) { // aOptions.replacement isn't set. @@ -533,12 +602,3 @@ this.NetUtil = { getService(Ci.nsIIOService); }, }; - -//////////////////////////////////////////////////////////////////////////////// -//// Initialization - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); - -// Define our lazy getters. -XPCOMUtils.defineLazyServiceGetter(this, "ioUtil", "@mozilla.org/io-util;1", - "nsIIOUtil"); diff --git a/netwerk/test/unit/test_NetUtil.js b/netwerk/test/unit/test_NetUtil.js index b81cd82ad94..e6ad1c63a53 100644 --- a/netwerk/test/unit/test_NetUtil.js +++ b/netwerk/test/unit/test_NetUtil.js @@ -261,7 +261,7 @@ function test_ioService() function test_asyncFetch_no_channel() { try { - NetUtil.asyncFetch2(null, function() { }); + NetUtil.asyncFetch(null, function() { }); do_throw("should throw!"); } catch (e) { @@ -274,7 +274,7 @@ function test_asyncFetch_no_channel() function test_asyncFetch_no_callback() { try { - NetUtil.asyncFetch2({ }); + NetUtil.asyncFetch({ }); do_throw("should throw!"); } catch (e) { @@ -298,19 +298,13 @@ function test_asyncFetch_with_nsIChannel() server.start(-1); // Create our channel. - let channel = NetUtil.ioService. - newChannel2("http://localhost:" + - server.identity.primaryPort + "/test", - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + let channel = NetUtil.newChannel({ + uri: "http://localhost:" + server.identity.primaryPort + "/test", + loadUsingSystemPrincipal: true, + }); // Open our channel asynchronously. - NetUtil.asyncFetch2(channel, function(aInputStream, aResult) { + NetUtil.asyncFetch(channel, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -344,7 +338,10 @@ function test_asyncFetch_with_nsIURI() server.identity.primaryPort + "/test"); // Open our URI asynchronously. - NetUtil.asyncFetch2(uri, function(aInputStream, aResult) { + NetUtil.asyncFetch({ + uri, + loadUsingSystemPrincipal: true, + }, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -379,9 +376,10 @@ function test_asyncFetch_with_string() server.start(-1); // Open our location asynchronously. - NetUtil.asyncFetch2("http://localhost:" + - server.identity.primaryPort + "/test", - function(aInputStream, aResult) { + NetUtil.asyncFetch({ + uri: "http://localhost:" + server.identity.primaryPort + "/test", + loadUsingSystemPrincipal: true, + }, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -423,7 +421,11 @@ function test_asyncFetch_with_nsIFile() do_check_eq(TEST_DATA, getFileContents(file)); // Open our file asynchronously. - NetUtil.asyncFetch2(file, function(aInputStream, aResult) { + // Note that this causes main-tread I/O and should be avoided in production. + NetUtil.asyncFetch({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true, + }, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -452,7 +454,7 @@ function test_asyncFetch_with_nsIInputString() istream.setData(TEST_DATA, TEST_DATA.length); // Read the input stream asynchronously. - NetUtil.asyncFetch2(istream, function(aInputStream, aResult) { + NetUtil.asyncFetch(istream, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -473,18 +475,13 @@ function test_asyncFetch_with_nsIInputString() function test_asyncFetch_does_not_block() { // Create our channel that has no data. - let channel = NetUtil.ioService. - newChannel2("data:text/plain,", - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + let channel = NetUtil.newChannel({ + uri: "data:text/plain,", + loadUsingSystemPrincipal: true, + }); // Open our channel asynchronously. - NetUtil.asyncFetch2(channel, function(aInputStream, aResult) { + NetUtil.asyncFetch(channel, function(aInputStream, aResult) { // Check that we had success. do_check_true(Components.isSuccessCode(aResult)); @@ -508,7 +505,7 @@ function test_asyncFetch_does_not_block() function test_newChannel_no_specifier() { try { - NetUtil.newChannel2(); + NetUtil.newChannel(); do_throw("should throw!"); } catch (e) { @@ -533,14 +530,7 @@ function test_newChannel_with_string() null, // aTriggeringPrincipal Ci.nsILoadInfo.SEC_NORMAL, Ci.nsIContentPolicy.TYPE_OTHER); - let NetUtilChannel = NetUtil.newChannel2(TEST_SPEC, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + let NetUtilChannel = NetUtil.newChannel(TEST_SPEC); do_check_true(iosChannel.URI.equals(NetUtilChannel.URI)); run_next_test(); @@ -559,14 +549,7 @@ function test_newChannel_with_nsIURI() null, // aTriggeringPrincipal Ci.nsILoadInfo.SEC_NORMAL, Ci.nsIContentPolicy.TYPE_OTHER); - let NetUtilChannel = NetUtil.newChannel2(uri, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + let NetUtilChannel = NetUtil.newChannel(uri); do_check_true(iosChannel.URI.equals(NetUtilChannel.URI)); run_next_test(); @@ -588,19 +571,83 @@ function test_newChannel_with_nsIFile() null, // aTriggeringPrincipal Ci.nsILoadInfo.SEC_NORMAL, Ci.nsIContentPolicy.TYPE_OTHER); - let NetUtilChannel = NetUtil.newChannel2(uri, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + let NetUtilChannel = NetUtil.newChannel(file); do_check_true(iosChannel.URI.equals(NetUtilChannel.URI)); run_next_test(); } +function test_newChannel_with_options() +{ + let uri = "data:text/plain,"; + + let iosChannel = NetUtil.ioService.newChannelFromURI2(NetUtil.newURI(uri), + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_NORMAL, + Ci.nsIContentPolicy.TYPE_OTHER); + + function checkEqualToIOSChannel(channel) { + do_check_true(iosChannel.URI.equals(channel.URI)); + } + + checkEqualToIOSChannel(NetUtil.newChannel({ + uri, + loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + })); + + checkEqualToIOSChannel(NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + })); + + run_next_test(); +} + +function test_newChannel_with_wrong_options() +{ + let uri = "data:text/plain,"; + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + + Assert.throws(() => { + NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true }, null, null); + }, /requires a single object argument/); + + Assert.throws(() => { + NetUtil.newChannel({}); + }, /requires the 'uri' property/); + + Assert.throws(() => { + NetUtil.newChannel({ uri }); + }, /requires at least one of the 'loadingNode'/); + + Assert.throws(() => { + NetUtil.newChannel({ + uri, + loadingPrincipal: systemPrincipal, + }); + }, /requires the 'contentPolicyType'/); + + Assert.throws(() => { + NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: systemPrincipal, + }); + }, /to be 'true' or 'undefined'/); + + Assert.throws(() => { + NetUtil.newChannel({ + uri, + loadingPrincipal: systemPrincipal, + loadUsingSystemPrincipal: true, + }); + }, /does not accept 'loadUsingSystemPrincipal'/); + + run_next_test(); +} + function test_readInputStreamToString() { const TEST_DATA = "this is a test string\0 with an embedded null"; @@ -756,6 +803,8 @@ function test_readInputStreamToString_invalid_sequence() test_newChannel_with_string, test_newChannel_with_nsIURI, test_newChannel_with_nsIFile, + test_newChannel_with_options, + test_newChannel_with_wrong_options, test_readInputStreamToString, test_readInputStreamToString_no_input_stream, test_readInputStreamToString_no_bytes_arg, diff --git a/toolkit/devtools/server/actors/webconsole.js b/toolkit/devtools/server/actors/webconsole.js index 4fc4236e749..2b00512f6f6 100644 --- a/toolkit/devtools/server/actors/webconsole.js +++ b/toolkit/devtools/server/actors/webconsole.js @@ -1060,11 +1060,17 @@ WebConsoleActor.prototype = */ evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {}) { + let trimmedString = aString.trim(); // The help function needs to be easy to guess, so we make the () optional. - if (aString.trim() == "help" || aString.trim() == "?") { + if (trimmedString == "help" || trimmedString == "?") { aString = "help()"; } + // Add easter egg for console.mihai(). + if (trimmedString == "console.mihai()" || trimmedString == "console.mihai();") { + aString = "\"http://incompleteness.me/blog/2015/02/09/console-dot-mihai/\""; + } + // Find the Debugger.Frame of the given FrameActor. let frame = null, frameActor = null; if (aOptions.frameActor) { diff --git a/tools/profiler/ProfileEntry.h b/tools/profiler/ProfileEntry.h index fff072cc572..bbfb9f447f4 100644 --- a/tools/profiler/ProfileEntry.h +++ b/tools/profiler/ProfileEntry.h @@ -75,7 +75,7 @@ typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagSt class ProfileBuffer { public: - NS_INLINE_DECL_REFCOUNTING(ProfileBuffer) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProfileBuffer) explicit ProfileBuffer(int aEntrySize);