mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 00:25:27 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
ba8a408b7d
@ -183,7 +183,11 @@ pref("app.update.metro.enabled", true);
|
||||
pref("app.update.silent", false);
|
||||
|
||||
// If set to true, the hamburger button will show badges for update events.
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
pref("app.update.badge", true);
|
||||
#else
|
||||
pref("app.update.badge", false);
|
||||
#endif
|
||||
|
||||
// If set to true, the Update Service will apply updates in the background
|
||||
// when it finishes downloading them.
|
||||
|
@ -86,6 +86,7 @@ let DevEdition = {
|
||||
this.styleSheet.removeEventListener("load", this);
|
||||
gBrowser.tabContainer._positionPinnedTabs();
|
||||
ToolbarIconColor.inferFromText();
|
||||
Services.obs.notifyObservers(window, "devedition-theme-state-changed", true);
|
||||
}
|
||||
},
|
||||
|
||||
@ -96,12 +97,15 @@ let DevEdition = {
|
||||
'xml-stylesheet', styleSheetAttr);
|
||||
this.styleSheet.addEventListener("load", this);
|
||||
document.insertBefore(this.styleSheet, document.documentElement);
|
||||
// NB: we'll notify observers once the stylesheet has fully loaded, see
|
||||
// handleEvent above.
|
||||
} else if (!deveditionThemeEnabled && this.styleSheet) {
|
||||
this.styleSheet.removeEventListener("load", this);
|
||||
this.styleSheet.remove();
|
||||
this.styleSheet = null;
|
||||
gBrowser.tabContainer._positionPinnedTabs();
|
||||
ToolbarIconColor.inferFromText();
|
||||
Services.obs.notifyObservers(window, "devedition-theme-state-changed", false);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -513,6 +513,8 @@
|
||||
<menuitem id="menu_browserToolbox"
|
||||
observes="devtoolsMenuBroadcaster_BrowserToolbox"
|
||||
accesskey="&browserToolboxMenu.accesskey;"/>
|
||||
<menuitem id="menu_browserContentToolbox"
|
||||
observes="devtoolsMenuBroadcaster_BrowserContentToolbox"/>
|
||||
<menuitem id="menu_browserConsole"
|
||||
observes="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
accesskey="&browserConsoleCmd.accesskey;"/>
|
||||
|
@ -100,6 +100,7 @@
|
||||
<command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
|
||||
<command id="Tools:WebIDE" oncommand="gDevToolsBrowser.openWebIDE();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserToolbox" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserContentToolbox" oncommand="gDevToolsBrowser.openContentProcessToolbox();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserConsole" oncommand="HUDService.toggleBrowserConsole();"/>
|
||||
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
|
||||
<command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
|
||||
@ -207,6 +208,9 @@
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserToolbox"
|
||||
label="&browserToolboxMenu.label;"
|
||||
command="Tools:BrowserToolbox"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserContentToolbox"
|
||||
label="&browserContentToolboxMenu.label;"
|
||||
command="Tools:BrowserContentToolbox"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
label="&browserConsoleCmd.label;"
|
||||
key="key_browserConsole"
|
||||
|
@ -474,7 +474,7 @@ skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: biz
|
||||
[browser_visibleTabs_bookmarkAllTabs.js]
|
||||
[browser_visibleTabs_contextMenu.js]
|
||||
[browser_visibleTabs_tabPreview.js]
|
||||
skip-if = (os == "win" && !debug) # Bug 1007418
|
||||
skip-if = (os == "win" && !debug) || e10s # Bug 1007418
|
||||
[browser_web_channel.js]
|
||||
[browser_windowopen_reflows.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
@ -19,6 +19,7 @@ const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
|
||||
const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
|
||||
const kDeveditionThemePref = "browser.devedition.theme.enabled";
|
||||
const kDeveditionButtonPref = "browser.devedition.theme.showCustomizeButton";
|
||||
const kDeveditionChangedNotification = "devedition-theme-state-changed";
|
||||
const kMaxTransitionDurationMs = 2000;
|
||||
|
||||
const kPanelItemContextMenu = "customizationPanelItemContextMenu";
|
||||
@ -64,9 +65,11 @@ function CustomizeMode(aWindow) {
|
||||
this.paletteEmptyNotice = this.document.getElementById("customization-empty");
|
||||
this.paletteSpacer = this.document.getElementById("customization-spacer");
|
||||
this.tipPanel = this.document.getElementById("customization-tipPanel");
|
||||
let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
|
||||
if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
|
||||
let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
|
||||
let deveditionButton = this.document.getElementById("customization-devedition-theme-button");
|
||||
lwthemeButton.setAttribute("hidden", "true");
|
||||
deveditionButton.setAttribute("hidden", "true");
|
||||
}
|
||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||
this._updateTitlebarButton();
|
||||
@ -74,7 +77,7 @@ function CustomizeMode(aWindow) {
|
||||
#endif
|
||||
this._updateDevEditionThemeButton();
|
||||
Services.prefs.addObserver(kDeveditionButtonPref, this, false);
|
||||
Services.prefs.addObserver(kDeveditionThemePref, this, false);
|
||||
Services.obs.addObserver(this, kDeveditionChangedNotification, false);
|
||||
this.window.addEventListener("unload", this);
|
||||
};
|
||||
|
||||
@ -111,7 +114,7 @@ CustomizeMode.prototype = {
|
||||
Services.prefs.removeObserver(kDrawInTitlebarPref, this);
|
||||
#endif
|
||||
Services.prefs.removeObserver(kDeveditionButtonPref, this);
|
||||
Services.prefs.removeObserver(kDeveditionThemePref, this);
|
||||
Services.obs.removeObserver(this, kDeveditionChangedNotification);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
@ -470,6 +473,7 @@ CustomizeMode.prototype = {
|
||||
toolbar.removeAttribute("customizing");
|
||||
|
||||
this.window.PanelUI.endBatchUpdate();
|
||||
delete this._lastLightweightTheme;
|
||||
this._changed = false;
|
||||
this._transitioning = false;
|
||||
this._handler.isExitingCustomizeMode = false;
|
||||
@ -1481,7 +1485,6 @@ CustomizeMode.prototype = {
|
||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||
this._updateTitlebarButton();
|
||||
#endif
|
||||
this._updateDevEditionThemeButton();
|
||||
break;
|
||||
case "lightweight-theme-window-updated":
|
||||
if (aSubject == this.window) {
|
||||
@ -1493,6 +1496,13 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kDeveditionChangedNotification:
|
||||
if (aSubject == this.window) {
|
||||
this._updateDevEditionThemeButton();
|
||||
this._updateResetButton();
|
||||
this._updateUndoResetButton();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1520,7 +1530,7 @@ CustomizeMode.prototype = {
|
||||
_updateDevEditionThemeButton: function() {
|
||||
let button = this.document.getElementById("customization-devedition-theme-button");
|
||||
|
||||
let themeEnabled = Services.prefs.getBoolPref(kDeveditionThemePref);
|
||||
let themeEnabled = !!this.window.DevEdition.styleSheet;
|
||||
if (themeEnabled) {
|
||||
button.setAttribute("checked", "true");
|
||||
} else {
|
||||
@ -1535,9 +1545,24 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
},
|
||||
toggleDevEditionTheme: function() {
|
||||
const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
|
||||
let button = this.document.getElementById("customization-devedition-theme-button");
|
||||
let preferenceValue = button.hasAttribute("checked");
|
||||
Services.prefs.setBoolPref(kDeveditionThemePref, preferenceValue);
|
||||
let shouldEnable = button.hasAttribute("checked");
|
||||
|
||||
Services.prefs.setBoolPref(kDeveditionThemePref, shouldEnable);
|
||||
let currentLWT = LightweightThemeManager.currentTheme;
|
||||
if (currentLWT && shouldEnable) {
|
||||
this._lastLightweightTheme = currentLWT;
|
||||
AddonManager.getAddonByID(DEFAULT_THEME_ID, function(aDefaultTheme) {
|
||||
// Theoretically, this could race if people are /very/ quick in switching
|
||||
// something else here, so doublecheck:
|
||||
if (button.hasAttribute("checked")) {
|
||||
aDefaultTheme.userDisabled = false;
|
||||
}
|
||||
});
|
||||
} else if (!currentLWT && !shouldEnable && this._lastLightweightTheme) {
|
||||
LightweightThemeManager.currentTheme = this._lastLightweightTheme;
|
||||
}
|
||||
},
|
||||
|
||||
_onDragStart: function(aEvent) {
|
||||
|
@ -114,6 +114,11 @@ add_task(function() {
|
||||
is(deveditionThemeButton.hasAttribute("checked"), defaultValue, "Devedition theme button should reflect pref value");
|
||||
is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test");
|
||||
Services.prefs.setBoolPref(prefName, !defaultValue);
|
||||
|
||||
//XXXgijs this line should be removed once bug 1094509 lands
|
||||
Services.prefs.setCharPref("devtools.theme", "dark");
|
||||
|
||||
yield waitForCondition(() => !restoreDefaultsButton.disabled);
|
||||
ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed");
|
||||
is(deveditionThemeButton.hasAttribute("checked"), !defaultValue, "Devedition theme button should reflect changed pref value");
|
||||
ok(!CustomizableUI.inDefaultState, "With devedition theme flipped, no longer default");
|
||||
@ -134,6 +139,8 @@ add_task(function() {
|
||||
is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
|
||||
is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
|
||||
|
||||
//XXXgijs this line should be removed once bug 1094509 lands
|
||||
Services.prefs.clearUserPref("devtools.theme");
|
||||
Services.prefs.clearUserPref(prefName);
|
||||
ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
|
||||
is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
|
||||
|
@ -273,6 +273,12 @@ loop.shared.actions = (function() {
|
||||
sessionToken: String,
|
||||
sessionId: String,
|
||||
expires: Number
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to indicate the user wishes to leave the room.
|
||||
*/
|
||||
LeaveRoom: Action.define("leaveRoom", {
|
||||
})
|
||||
};
|
||||
})();
|
||||
|
@ -52,10 +52,12 @@ loop.store.ActiveRoomStore = (function() {
|
||||
this._dispatcher.register(this, [
|
||||
"roomFailure",
|
||||
"setupWindowData",
|
||||
"fetchServerData",
|
||||
"updateRoomInfo",
|
||||
"joinRoom",
|
||||
"joinedRoom",
|
||||
"windowUnload"
|
||||
"windowUnload",
|
||||
"leaveRoom"
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -151,6 +153,26 @@ loop.store.ActiveRoomStore = (function() {
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute fetchServerData event action from the dispatcher. Although
|
||||
* this is to fetch the server data - for rooms on the standalone client,
|
||||
* we don't actually need to get any data. Therefore we just save the
|
||||
* data that is given to us for when the user chooses to join the room.
|
||||
*
|
||||
* @param {sharedActions.FetchServerData} actionData
|
||||
*/
|
||||
fetchServerData: function(actionData) {
|
||||
if (actionData.windowType !== "room") {
|
||||
// Nothing for us to do here, leave it to other stores.
|
||||
return;
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
roomToken: actionData.token,
|
||||
roomState: ROOM_STATES.READY
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateRoomInfo action. Updates the room data and
|
||||
* sets the state to `READY`.
|
||||
@ -213,6 +235,13 @@ loop.store.ActiveRoomStore = (function() {
|
||||
this._leaveRoom();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a room being left.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this._leaveRoom();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles setting of the refresh timeout callback.
|
||||
*
|
||||
|
@ -99,6 +99,7 @@
|
||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneAppStore.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneClient.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneMozLoop.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneRoomViews.js"></script>
|
||||
<script type="text/javascript" src="js/webapp.js"></script>
|
||||
|
||||
|
@ -0,0 +1,188 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global loop:true */
|
||||
|
||||
/**
|
||||
* The StandaloneMozLoop implementation reflects that of the mozLoop API for Loop
|
||||
* in the desktop code. Not all functions are implemented.
|
||||
*/
|
||||
var loop = loop || {};
|
||||
loop.StandaloneMozLoop = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The maximum number of clients that we currently support.
|
||||
*/
|
||||
var ROOM_MAX_CLIENTS = 2;
|
||||
|
||||
|
||||
/**
|
||||
* Validates a data object to confirm it has the specified properties.
|
||||
*
|
||||
* @param {Object} data The data object to verify
|
||||
* @param {Array} schema The validation schema
|
||||
* @return Returns all properties if valid, or an empty object if no properties
|
||||
* have been specified.
|
||||
*/
|
||||
function validate(data, schema) {
|
||||
if (!schema) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return new loop.validate.Validator(schema).validate(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for XHR failures.
|
||||
*
|
||||
* @param {Function} callback Callback(err)
|
||||
* @param jqXHR See jQuery docs
|
||||
* @param textStatus See jQuery docs
|
||||
* @param errorThrown See jQuery docs
|
||||
*/
|
||||
function failureHandler(callback, jqXHR, textStatus, errorThrown) {
|
||||
var jsonErr = jqXHR && jqXHR.responseJSON || {};
|
||||
var message = "HTTP " + jqXHR.status + " " + errorThrown;
|
||||
|
||||
// Create an error with server error `errno` code attached as a property
|
||||
var err = new Error(message);
|
||||
err.errno = jsonErr.errno;
|
||||
|
||||
callback(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* StandaloneMozLoopRooms is used as part of StandaloneMozLoop to define
|
||||
* the rooms sub-object. We do it this way so that we can share the options
|
||||
* information from the parent.
|
||||
*/
|
||||
var StandaloneMozLoopRooms = function(options) {
|
||||
options = options || {};
|
||||
if (!options.baseServerUrl) {
|
||||
throw new Error("missing required baseServerUrl");
|
||||
}
|
||||
|
||||
this._baseServerUrl = options.baseServerUrl;
|
||||
};
|
||||
|
||||
StandaloneMozLoopRooms.prototype = {
|
||||
/**
|
||||
* Internal function to actually perform a post to a room.
|
||||
*
|
||||
* @param {String} roomToken The rom token.
|
||||
* @param {String} sessionToken The sessionToken for the room if known
|
||||
* @param {Object} roomData The data to send with the request
|
||||
* @param {Array} expectedProps The expected properties we should receive from the
|
||||
* server
|
||||
* @param {Function} callback The callback for when the request completes. The
|
||||
* first parameter is non-null on error, the second parameter
|
||||
* is the response data.
|
||||
*/
|
||||
_postToRoom: function(roomToken, sessionToken, roomData, expectedProps, callback) {
|
||||
var req = $.ajax({
|
||||
url: this._baseServerUrl + "/rooms/" + roomToken,
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(roomData),
|
||||
beforeSend: function(xhr) {
|
||||
if (sessionToken) {
|
||||
xhr.setRequestHeader("Authorization", "Basic " + btoa(sessionToken));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
req.done(function(responseData) {
|
||||
try {
|
||||
callback(null, validate(responseData, expectedProps));
|
||||
} catch (err) {
|
||||
console.error("Error requesting call info", err.message);
|
||||
callback(err);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
req.fail(failureHandler.bind(this, callback));
|
||||
},
|
||||
|
||||
/**
|
||||
* Joins a room
|
||||
*
|
||||
* @param {String} roomToken The room token.
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`.
|
||||
*/
|
||||
join: function(roomToken, callback) {
|
||||
this._postToRoom(roomToken, null, {
|
||||
action: "join",
|
||||
displayName: mozL10n.get("rooms_display_name_guest"),
|
||||
clientMaxSize: ROOM_MAX_CLIENTS
|
||||
}, {
|
||||
apiKey: String,
|
||||
sessionId: String,
|
||||
sessionToken: String,
|
||||
expires: Number
|
||||
}, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Refreshes a room
|
||||
*
|
||||
* @param {String} roomToken The room token.
|
||||
* @param {String} sessionToken The session token for the session that has been
|
||||
* joined
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`.
|
||||
*/
|
||||
refreshMembership: function(roomToken, sessionToken, callback) {
|
||||
this._postToRoom(roomToken, sessionToken, {
|
||||
action: "refresh",
|
||||
sessionToken: sessionToken
|
||||
}, {
|
||||
expires: Number
|
||||
}, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Leaves a room. Although this is an sync function, no data is returned
|
||||
* from the server.
|
||||
*
|
||||
* @param {String} roomToken The room token.
|
||||
* @param {String} sessionToken The session token for the session that has been
|
||||
* joined
|
||||
* @param {Function} callback Optional. Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`.
|
||||
*/
|
||||
leave: function(roomToken, sessionToken, callback) {
|
||||
if (!callback) {
|
||||
callback = function(error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this._postToRoom(roomToken, sessionToken, {
|
||||
action: "leave",
|
||||
sessionToken: sessionToken
|
||||
}, null, callback);
|
||||
}
|
||||
};
|
||||
|
||||
var StandaloneMozLoop = function(options) {
|
||||
options = options || {};
|
||||
if (!options.baseServerUrl) {
|
||||
throw new Error("missing required baseServerUrl");
|
||||
}
|
||||
|
||||
this._baseServerUrl = options.baseServerUrl;
|
||||
|
||||
this.rooms = new StandaloneMozLoopRooms(options);
|
||||
};
|
||||
|
||||
return StandaloneMozLoop;
|
||||
})(navigator.mozL10n);
|
@ -10,12 +10,16 @@ var loop = loop || {};
|
||||
loop.standaloneRoomViews = (function() {
|
||||
"use strict";
|
||||
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
activeRoomStore:
|
||||
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired
|
||||
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -41,12 +45,34 @@ loop.standaloneRoomViews = (function() {
|
||||
this.stopListening(this.props.activeRoomStore);
|
||||
},
|
||||
|
||||
joinRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||
},
|
||||
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
// XXX Implement tests for this view when we do the proper views
|
||||
// - bug 1074705 and others
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.div(null, this.state.roomState)
|
||||
)
|
||||
);
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.READY: {
|
||||
return (
|
||||
React.DOM.div(null, React.DOM.button({onClick: this.joinRoom}, "Join"))
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.JOINED: {
|
||||
return (
|
||||
React.DOM.div(null, React.DOM.button({onClick: this.leaveRoom}, "Leave"))
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
React.DOM.div(null, this.state.roomState)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -10,12 +10,16 @@ var loop = loop || {};
|
||||
loop.standaloneRoomViews = (function() {
|
||||
"use strict";
|
||||
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
var StandaloneRoomView = React.createClass({
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
activeRoomStore:
|
||||
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired
|
||||
React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -41,12 +45,34 @@ loop.standaloneRoomViews = (function() {
|
||||
this.stopListening(this.props.activeRoomStore);
|
||||
},
|
||||
|
||||
joinRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
|
||||
},
|
||||
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
// XXX Implement tests for this view when we do the proper views
|
||||
// - bug 1074705 and others
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<div>{this.state.roomState}</div>
|
||||
</div>
|
||||
);
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.READY: {
|
||||
return (
|
||||
<div><button onClick={this.joinRoom}>Join</button></div>
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.JOINED: {
|
||||
return (
|
||||
<div><button onClick={this.leaveRoom}>Leave</button></div>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div>{this.state.roomState}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -893,7 +893,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
standaloneAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.StandaloneAppStore).isRequired,
|
||||
activeRoomStore: React.PropTypes.instanceOf(
|
||||
loop.store.ActiveRoomStore).isRequired
|
||||
loop.store.ActiveRoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -937,7 +938,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
case "room": {
|
||||
return (
|
||||
loop.standaloneRoomViews.StandaloneRoomView({
|
||||
activeRoomStore: this.props.activeRoomStore}
|
||||
activeRoomStore: this.props.activeRoomStore,
|
||||
dispatcher: this.props.dispatcher}
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -958,6 +960,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
function init() {
|
||||
var helper = new sharedUtils.Helper();
|
||||
var standaloneMozLoop = new loop.StandaloneMozLoop({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
|
||||
// Older non-flux based items.
|
||||
var notifications = new sharedModels.NotificationCollection();
|
||||
@ -991,9 +996,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
// XXX Bug 1074702 will introduce a mozLoop compatible object for
|
||||
// the standalone rooms.
|
||||
mozLoop: {}
|
||||
mozLoop: standaloneMozLoop
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||
});
|
||||
|
||||
React.renderComponent(WebappRootView({
|
||||
@ -1004,7 +1011,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
sdk: OT,
|
||||
feedbackApiClient: feedbackApiClient,
|
||||
standaloneAppStore: standaloneAppStore,
|
||||
activeRoomStore: activeRoomStore}
|
||||
activeRoomStore: activeRoomStore,
|
||||
dispatcher: dispatcher}
|
||||
), document.querySelector("#main"));
|
||||
|
||||
// Set the 'lang' and 'dir' attributes to <html> when the page is translated
|
||||
|
@ -893,7 +893,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
standaloneAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.StandaloneAppStore).isRequired,
|
||||
activeRoomStore: React.PropTypes.instanceOf(
|
||||
loop.store.ActiveRoomStore).isRequired
|
||||
loop.store.ActiveRoomStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -938,6 +939,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
return (
|
||||
<loop.standaloneRoomViews.StandaloneRoomView
|
||||
activeRoomStore={this.props.activeRoomStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -958,6 +960,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
function init() {
|
||||
var helper = new sharedUtils.Helper();
|
||||
var standaloneMozLoop = new loop.StandaloneMozLoop({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
|
||||
// Older non-flux based items.
|
||||
var notifications = new sharedModels.NotificationCollection();
|
||||
@ -991,9 +996,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var activeRoomStore = new loop.store.ActiveRoomStore({
|
||||
dispatcher: dispatcher,
|
||||
// XXX Bug 1074702 will introduce a mozLoop compatible object for
|
||||
// the standalone rooms.
|
||||
mozLoop: {}
|
||||
mozLoop: standaloneMozLoop
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||
});
|
||||
|
||||
React.renderComponent(<WebappRootView
|
||||
@ -1005,6 +1012,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
feedbackApiClient={feedbackApiClient}
|
||||
standaloneAppStore={standaloneAppStore}
|
||||
activeRoomStore={activeRoomStore}
|
||||
dispatcher={dispatcher}
|
||||
/>, document.querySelector("#main"));
|
||||
|
||||
// Set the 'lang' and 'dir' attributes to <html> when the page is translated
|
||||
|
@ -115,6 +115,7 @@ rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start
|
||||
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
||||
rooms_room_joined_label=Someone has joined the conversation!
|
||||
rooms_room_join_label=Join the conversation
|
||||
rooms_display_name_guest=Guest
|
||||
|
||||
## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
|
||||
## replaced by the brand name and {{currentStatus}} will be replaced
|
||||
|
@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loop desktop-local mocha tests</title>
|
||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-1.17.1.css">
|
||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
@ -22,7 +22,7 @@
|
||||
<script src="../../content/shared/libs/backbone-1.1.2.js"></script>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<script src="../shared/vendor/mocha-1.17.1.js"></script>
|
||||
<script src="../shared/vendor/mocha-2.0.1.js"></script>
|
||||
<script src="../shared/vendor/chai-1.9.0.js"></script>
|
||||
<script src="../shared/vendor/sinon-1.9.0.js"></script>
|
||||
<script>
|
||||
|
@ -159,6 +159,26 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fetchServerData", function() {
|
||||
it("should save the token", function() {
|
||||
store.fetchServerData(new sharedActions.FetchServerData({
|
||||
windowType: "room",
|
||||
token: "fakeToken"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().roomToken).eql("fakeToken");
|
||||
});
|
||||
|
||||
it("should set the state to `READY`", function() {
|
||||
store.fetchServerData(new sharedActions.FetchServerData({
|
||||
windowType: "room",
|
||||
token: "fakeToken"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomInfo", function() {
|
||||
var fakeRoomInfo;
|
||||
|
||||
@ -172,13 +192,13 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
it("should set the state to READY", function() {
|
||||
store.updateRoomInfo(fakeRoomInfo);
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
|
||||
it("should save the room information", function() {
|
||||
store.updateRoomInfo(fakeRoomInfo);
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.roomName).eql(fakeRoomInfo.roomName);
|
||||
@ -247,13 +267,13 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
it("should set the state to `JOINED`", function() {
|
||||
store.joinedRoom(fakeJoinedData);
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
|
||||
});
|
||||
|
||||
it("should store the session and api values", function() {
|
||||
store.joinedRoom(fakeJoinedData);
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.apiKey).eql(fakeJoinedData.apiKey);
|
||||
@ -263,7 +283,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
|
||||
it("should call mozLoop.rooms.refreshMembership before the expiresTime",
|
||||
function() {
|
||||
store.joinedRoom(fakeJoinedData);
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
sandbox.clock.tick(fakeJoinedData.expires * 1000);
|
||||
|
||||
@ -277,7 +297,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
fakeMozLoop.rooms.refreshMembership.callsArgWith(2,
|
||||
null, {expires: 40});
|
||||
|
||||
store.joinedRoom(fakeJoinedData);
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
// Clock tick for the first expiry time (which
|
||||
// sets up the refreshMembership).
|
||||
@ -296,7 +316,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
var fakeError = new Error("fake");
|
||||
fakeMozLoop.rooms.refreshMembership.callsArgWith(2, fakeError);
|
||||
|
||||
store.joinedRoom(fakeJoinedData);
|
||||
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
|
||||
|
||||
// Clock tick for the first expiry time (which
|
||||
// sets up the refreshMembership).
|
||||
@ -342,4 +362,37 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#leaveRoom", function() {
|
||||
beforeEach(function() {
|
||||
store.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED,
|
||||
roomToken: "fakeToken",
|
||||
sessionToken: "1627384950"
|
||||
});
|
||||
});
|
||||
|
||||
it("should clear any existing timeout", function() {
|
||||
sandbox.stub(window, "clearTimeout");
|
||||
store._timeout = {};
|
||||
|
||||
store.leaveRoom();
|
||||
|
||||
sinon.assert.calledOnce(clearTimeout);
|
||||
});
|
||||
|
||||
it("should call mozLoop.rooms.leave", function() {
|
||||
store.leaveRoom();
|
||||
|
||||
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
|
||||
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
|
||||
"fakeToken", "1627384950");
|
||||
});
|
||||
|
||||
it("should set the state to ready", function() {
|
||||
store.leaveRoom();
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loop shared mocha tests</title>
|
||||
<link rel="stylesheet" media="all" href="vendor/mocha-1.17.1.css">
|
||||
<link rel="stylesheet" media="all" href="vendor/mocha-2.0.1.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
@ -23,7 +23,7 @@
|
||||
<script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<script src="vendor/mocha-1.17.1.js"></script>
|
||||
<script src="vendor/mocha-2.0.1.js"></script>
|
||||
<script src="vendor/chai-1.9.0.js"></script>
|
||||
<script src="vendor/sinon-1.9.0.js"></script>
|
||||
<script>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loop mocha tests</title>
|
||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-1.17.1.css">
|
||||
<link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
@ -22,7 +22,7 @@
|
||||
<script src="../../content/shared/libs/backbone-1.1.2.js"></script>
|
||||
<script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
|
||||
<!-- test dependencies -->
|
||||
<script src="../shared/vendor/mocha-1.17.1.js"></script>
|
||||
<script src="../shared/vendor/mocha-2.0.1.js"></script>
|
||||
<script src="../shared/vendor/chai-1.9.0.js"></script>
|
||||
<script src="../shared/vendor/sinon-1.9.0.js"></script>
|
||||
<script src="../shared/sdk_mock.js"></script>
|
||||
@ -44,11 +44,13 @@
|
||||
<script src="../../standalone/content/js/multiplexGum.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneAppStore.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneClient.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneMozLoop.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneRoomViews.js"></script>
|
||||
<script src="../../standalone/content/js/webapp.js"></script>
|
||||
<!-- Test scripts -->
|
||||
<!-- Test scripts -->
|
||||
<script src="standalone_client_test.js"></script>
|
||||
<script src="standaloneAppStore_test.js"></script>
|
||||
<script src="standaloneMozLoop_test.js"></script>
|
||||
<script src="webapp_test.js"></script>
|
||||
<script src="multiplexGum_test.js"></script>
|
||||
<script>
|
||||
|
@ -199,7 +199,7 @@ describe("loop.standaloneMedia._MultiplexGum", function() {
|
||||
|
||||
it("should not call a getPermsAndCacheMedia success callback at the time" +
|
||||
" of gUM success callback fires",
|
||||
function(done) {
|
||||
function() {
|
||||
var fakeLocalStream = {};
|
||||
multiplexGum.userMedia.localStream = fakeLocalStream;
|
||||
navigator.originalGum.callsArgWith(1, fakeLocalStream);
|
||||
@ -219,23 +219,21 @@ describe("loop.standaloneMedia._MultiplexGum", function() {
|
||||
}, function() {
|
||||
sinon.assert.fail("error callback should not have fired");
|
||||
reject();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
promiseCalledOnce.then(function() {
|
||||
return promiseCalledOnce.then(function() {
|
||||
defaultGum(null, function gUMSuccess(localStream2) {
|
||||
expect(localStream2).to.eql(fakeLocalStream);
|
||||
expect(multiplexGum.userMedia).to.have.property('pending', false);
|
||||
expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not call a getPermsAndCacheMedia error callback when the " +
|
||||
" gUM error callback fires",
|
||||
function(done) {
|
||||
function() {
|
||||
var fakeError = "monkeys ate the stream";
|
||||
multiplexGum.userMedia.error = fakeError;
|
||||
navigator.originalGum.callsArgWith(2, fakeError);
|
||||
@ -244,7 +242,6 @@ describe("loop.standaloneMedia._MultiplexGum", function() {
|
||||
multiplexGum.getPermsAndCacheMedia(null, function() {
|
||||
sinon.assert.fail("success callback should not have fired");
|
||||
reject();
|
||||
done();
|
||||
}, function gPACMError(errString) {
|
||||
expect(errString).to.eql(fakeError);
|
||||
expect(multiplexGum.userMedia).to.have.property('pending', false);
|
||||
@ -256,12 +253,11 @@ describe("loop.standaloneMedia._MultiplexGum", function() {
|
||||
});
|
||||
});
|
||||
|
||||
promiseCalledOnce.then(function() {
|
||||
return promiseCalledOnce.then(function() {
|
||||
defaultGum(null, function() {},
|
||||
function gUMError(errString) {
|
||||
expect(errString).to.eql(fakeError);
|
||||
expect(multiplexGum.userMedia).to.have.property('pending', false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,178 @@
|
||||
/* 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/. */
|
||||
|
||||
var expect = chai.expect;
|
||||
|
||||
describe("loop.StandaloneMozLoop", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox, fakeXHR, requests, callback, mozLoop;
|
||||
var fakeToken, fakeBaseServerUrl, fakeServerErrorDescription;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
// https://github.com/cjohansen/Sinon.JS/issues/393
|
||||
fakeXHR.xhr.onCreate = function (xhr) {
|
||||
requests.push(xhr);
|
||||
};
|
||||
fakeBaseServerUrl = "http://fake.api";
|
||||
fakeServerErrorDescription = {
|
||||
code: 401,
|
||||
errno: 101,
|
||||
error: "error",
|
||||
message: "invalid token",
|
||||
info: "error info"
|
||||
};
|
||||
|
||||
callback = sinon.spy();
|
||||
|
||||
mozLoop = new loop.StandaloneMozLoop({
|
||||
baseServerUrl: fakeBaseServerUrl
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe("#constructor", function() {
|
||||
it("should require a baseServerUrl setting", function() {
|
||||
expect(function() {
|
||||
new loop.StandaloneMozLoop();
|
||||
}).to.Throw(Error, /required/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#rooms.join", function() {
|
||||
it("should POST to the server", function() {
|
||||
mozLoop.rooms.join("fakeToken", callback);
|
||||
|
||||
expect(requests).to.have.length.of(1);
|
||||
expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
|
||||
expect(requests[0].method).eql("POST");
|
||||
|
||||
var requestData = JSON.parse(requests[0].requestBody);
|
||||
expect(requestData.action).eql("join");
|
||||
});
|
||||
|
||||
it("should call the callback with success parameters", function() {
|
||||
mozLoop.rooms.join("fakeToken", callback);
|
||||
|
||||
var sessionData = {
|
||||
apiKey: "12345",
|
||||
sessionId: "54321",
|
||||
sessionToken: "another token",
|
||||
expires: 20
|
||||
};
|
||||
|
||||
requests[0].respond(200, {"Content-Type": "application/json"},
|
||||
JSON.stringify(sessionData));
|
||||
|
||||
sinon.assert.calledOnce(callback);
|
||||
sinon.assert.calledWithExactly(callback, null, sessionData);
|
||||
});
|
||||
|
||||
it("should call the callback with failure parameters", function() {
|
||||
mozLoop.rooms.join("fakeToken", callback);
|
||||
|
||||
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||
JSON.stringify(fakeServerErrorDescription));
|
||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||
return /HTTP 401 Unauthorized/.test(err.message);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#rooms.refreshMembership", function() {
|
||||
var mozLoop, fakeServerErrorDescription;
|
||||
|
||||
beforeEach(function() {
|
||||
mozLoop = new loop.StandaloneMozLoop({
|
||||
baseServerUrl: fakeBaseServerUrl
|
||||
});
|
||||
|
||||
fakeServerErrorDescription = {
|
||||
code: 401,
|
||||
errno: 101,
|
||||
error: "error",
|
||||
message: "invalid token",
|
||||
info: "error info"
|
||||
};
|
||||
});
|
||||
|
||||
it("should POST to the server", function() {
|
||||
mozLoop.rooms.refreshMembership("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
expect(requests).to.have.length.of(1);
|
||||
expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
|
||||
expect(requests[0].method).eql("POST");
|
||||
expect(requests[0].requestHeaders.Authorization)
|
||||
.eql("Basic " + btoa("fakeSessionToken"));
|
||||
|
||||
var requestData = JSON.parse(requests[0].requestBody);
|
||||
expect(requestData.action).eql("refresh");
|
||||
expect(requestData.sessionToken).eql("fakeSessionToken");
|
||||
});
|
||||
|
||||
it("should call the callback with success parameters", function() {
|
||||
mozLoop.rooms.refreshMembership("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
var responseData = {
|
||||
expires: 20
|
||||
};
|
||||
|
||||
requests[0].respond(200, {"Content-Type": "application/json"},
|
||||
JSON.stringify(responseData));
|
||||
|
||||
sinon.assert.calledOnce(callback);
|
||||
sinon.assert.calledWithExactly(callback, null, responseData);
|
||||
});
|
||||
|
||||
it("should call the callback with failure parameters", function() {
|
||||
mozLoop.rooms.refreshMembership("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||
JSON.stringify(fakeServerErrorDescription));
|
||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||
return /HTTP 401 Unauthorized/.test(err.message);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#rooms.leave", function() {
|
||||
it("should POST to the server", function() {
|
||||
mozLoop.rooms.leave("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
expect(requests).to.have.length.of(1);
|
||||
expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
|
||||
expect(requests[0].method).eql("POST");
|
||||
expect(requests[0].requestHeaders.Authorization)
|
||||
.eql("Basic " + btoa("fakeSessionToken"));
|
||||
|
||||
var requestData = JSON.parse(requests[0].requestBody);
|
||||
expect(requestData.action).eql("leave");
|
||||
});
|
||||
|
||||
it("should call the callback with success parameters", function() {
|
||||
mozLoop.rooms.leave("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
requests[0].respond(204);
|
||||
|
||||
sinon.assert.calledOnce(callback);
|
||||
sinon.assert.calledWithExactly(callback, null, {});
|
||||
});
|
||||
|
||||
it("should call the callback with failure parameters", function() {
|
||||
mozLoop.rooms.leave("fakeToken", "fakeSessionToken", callback);
|
||||
|
||||
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||
JSON.stringify(fakeServerErrorDescription));
|
||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||
return /HTTP 401 Unauthorized/.test(err.message);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
@ -20,11 +20,18 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
|
||||
"resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
|
||||
const bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
|
||||
|
||||
/**
|
||||
* The method name to use for ES6 iteration. If symbols are enabled in this
|
||||
* build, use Symbol.iterator; otherwise "@@iterator".
|
||||
@ -593,6 +600,7 @@ let gDevToolsBrowser = {
|
||||
let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled &&
|
||||
Services.prefs.getBoolPref("devtools.debugger.chrome-enabled");
|
||||
toggleCmd("Tools:BrowserToolbox", remoteEnabled);
|
||||
toggleCmd("Tools:BrowserContentToolbox", remoteEnabled && win.gMultiProcessBrowser);
|
||||
|
||||
// Enable Error Console?
|
||||
let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
|
||||
@ -688,6 +696,61 @@ let gDevToolsBrowser = {
|
||||
}
|
||||
},
|
||||
|
||||
_getContentProcessTarget: function () {
|
||||
// Create a DebuggerServer in order to connect locally to it
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
let client = new DebuggerClient(transport);
|
||||
|
||||
let deferred = promise.defer();
|
||||
client.connect(() => {
|
||||
client.mainRoot.listProcesses(response => {
|
||||
// Do nothing if there is only one process, the parent process.
|
||||
let contentProcesses = response.processes.filter(p => (!p.parent));
|
||||
if (contentProcesses.length < 1) {
|
||||
let msg = bundle.GetStringFromName("toolbox.noContentProcess.message");
|
||||
Services.prompt.alert(null, "", msg);
|
||||
deferred.reject("No content processes available.");
|
||||
return;
|
||||
}
|
||||
// Otherwise, arbitrary connect to the unique content process.
|
||||
client.attachProcess(contentProcesses[0].id)
|
||||
.then(response => {
|
||||
let options = {
|
||||
form: response.form,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
return devtools.TargetFactory.forRemoteTab(options);
|
||||
})
|
||||
.then(target => {
|
||||
// Ensure closing the connection in order to cleanup
|
||||
// the debugger client and also the server created in the
|
||||
// content process
|
||||
target.on("close", () => {
|
||||
client.close();
|
||||
});
|
||||
deferred.resolve(target);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
openContentProcessToolbox: function () {
|
||||
this._getContentProcessTarget()
|
||||
.then(target => {
|
||||
// Display a new toolbox, in a new window, with debugger by default
|
||||
return gDevTools.showToolbox(target, "jsdebugger",
|
||||
devtools.Toolbox.HostType.WINDOW);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Install WebIDE widget
|
||||
*/
|
||||
|
320
browser/devtools/webide/content/devicepreferences.js
Normal file
320
browser/devtools/webide/content/devicepreferences.js
Normal file
@ -0,0 +1,320 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
const {AppManager} = require("devtools/webide/app-manager");
|
||||
const {Connection} = require("devtools/client/connection-manager");
|
||||
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
||||
|
||||
let devicePrefsKeys = {};
|
||||
let table;
|
||||
|
||||
window.addEventListener("load", function onLoad() {
|
||||
window.removeEventListener("load", onLoad);
|
||||
document.getElementById("close").onclick = CloseUI;
|
||||
AppManager.on("app-manager-update", OnAppManagerUpdate);
|
||||
document.getElementById("device-preferences").onchange = UpdatePref;
|
||||
document.getElementById("device-preferences").onclick = CheckReset;
|
||||
document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchPref;
|
||||
document.getElementById("custom-value").onclick = UpdateNewPref;
|
||||
document.getElementById("custom-value-type").onchange = ClearNewPrefs;
|
||||
document.getElementById("add-custom-preference").onkeyup = CheckNewPrefSubmit;
|
||||
BuildUI();
|
||||
}, true);
|
||||
|
||||
window.addEventListener("unload", function onUnload() {
|
||||
window.removeEventListener("unload", onUnload);
|
||||
AppManager.off("app-manager-update", OnAppManagerUpdate);
|
||||
});
|
||||
|
||||
function CloseUI() {
|
||||
window.parent.UI.openProject();
|
||||
}
|
||||
|
||||
function OnAppManagerUpdate(event, what) {
|
||||
if (what == "connection" || what == "list-tabs-response") {
|
||||
BuildUI();
|
||||
}
|
||||
}
|
||||
|
||||
function RenderByType(input, name, value, customType) {
|
||||
value = customType || typeof value;
|
||||
|
||||
switch (value) {
|
||||
case "boolean":
|
||||
input.setAttribute("data-type", "boolean");
|
||||
input.setAttribute("type", "checkbox");
|
||||
break;
|
||||
case "number":
|
||||
input.setAttribute("data-type", "number");
|
||||
input.setAttribute("type", "number");
|
||||
break;
|
||||
default:
|
||||
input.setAttribute("data-type", "string");
|
||||
input.setAttribute("type", "text");
|
||||
break;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
let defaultPref; // Used by tests
|
||||
function ResetToDefault(name, input, button) {
|
||||
AppManager.preferenceFront.clearUserPref(name);
|
||||
let dataType = input.getAttribute("data-type");
|
||||
let tr = document.getElementById("row-" + name);
|
||||
|
||||
switch (dataType) {
|
||||
case "boolean":
|
||||
defaultPref = AppManager.preferenceFront.getBoolPref(name);
|
||||
defaultPref.then(boolean => {
|
||||
input.checked = boolean;
|
||||
}, () => {
|
||||
input.checked = false;
|
||||
tr.parentNode.removeChild(tr);
|
||||
});
|
||||
|
||||
break;
|
||||
case "number":
|
||||
defaultPref = AppManager.preferenceFront.getIntPref(name);
|
||||
defaultPref.then(number => {
|
||||
input.value = number;
|
||||
}, () => {
|
||||
tr.parentNode.removeChild(tr);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
defaultPref = AppManager.preferenceFront.getCharPref(name);
|
||||
defaultPref.then(string => {
|
||||
input.value = string;
|
||||
}, () => {
|
||||
tr.parentNode.removeChild(tr);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
button.classList.add("hide");
|
||||
}
|
||||
|
||||
function SaveByType(options) {
|
||||
let prefName = options.id;
|
||||
let inputType = options.type;
|
||||
let value = options.value;
|
||||
let input = document.getElementById(prefName);
|
||||
|
||||
switch (inputType) {
|
||||
case "boolean":
|
||||
AppManager.preferenceFront.setBoolPref(prefName, input.checked);
|
||||
break;
|
||||
case "number":
|
||||
AppManager.preferenceFront.setIntPref(prefName, value);
|
||||
break;
|
||||
default:
|
||||
AppManager.preferenceFront.setCharPref(prefName, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function CheckNewPrefSubmit(event) {
|
||||
if (event.keyCode === 13) {
|
||||
document.getElementById("custom-value").click();
|
||||
}
|
||||
}
|
||||
|
||||
function ClearNewPrefs() {
|
||||
let customTextEl = table.querySelector("#custom-value-text");
|
||||
if (customTextEl.checked) {
|
||||
customTextEl.checked = false;
|
||||
} else {
|
||||
customTextEl.value = "";
|
||||
}
|
||||
|
||||
UpdateFieldType();
|
||||
}
|
||||
|
||||
function UpdateFieldType() {
|
||||
let customValueType = table.querySelector("#custom-value-type").value;
|
||||
let customTextEl = table.querySelector("#custom-value-text");
|
||||
let customText = customTextEl.value;
|
||||
|
||||
if (customValueType.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (customValueType) {
|
||||
case "boolean":
|
||||
customTextEl.type = "checkbox";
|
||||
customText = customTextEl.checked;
|
||||
break;
|
||||
case "number":
|
||||
customText = parseInt(customText, 10) || 0;
|
||||
customTextEl.type = "number";
|
||||
break;
|
||||
default:
|
||||
customTextEl.type = "text";
|
||||
break;
|
||||
}
|
||||
|
||||
return customValueType;
|
||||
}
|
||||
|
||||
function UpdateNewPref(event) {
|
||||
let customValueType = UpdateFieldType();
|
||||
|
||||
if (!customValueType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let customRow = table.querySelector("tr:nth-of-type(2)");
|
||||
let customTextEl = table.querySelector("#custom-value-text");
|
||||
let customTextNameEl = table.querySelector("#custom-value-name");
|
||||
|
||||
if (customTextEl.validity.valid) {
|
||||
let customText = customTextEl.value;
|
||||
|
||||
if (customValueType === "boolean") {
|
||||
customText = customTextEl.checked;
|
||||
}
|
||||
let customTextName = customTextNameEl.value.replace(/[^A-Za-z0-9\.\-_]/gi, "");
|
||||
GenerateField(customTextName, customText, true, customValueType, customRow);
|
||||
SaveByType({
|
||||
id: customTextName,
|
||||
type: customValueType,
|
||||
value: customText
|
||||
});
|
||||
customTextNameEl.value = "";
|
||||
ClearNewPrefs();
|
||||
}
|
||||
}
|
||||
|
||||
function CheckReset(event) {
|
||||
if (event.target.classList.contains("reset")) {
|
||||
let btnId = event.target.getAttribute("data-id");
|
||||
let input = document.getElementById(btnId);
|
||||
ResetToDefault(btnId, input, event.target);
|
||||
}
|
||||
}
|
||||
|
||||
function UpdatePref(event) {
|
||||
if (event.target) {
|
||||
let inputType = event.target.getAttribute("data-type");
|
||||
let inputValue = event.target.checked || event.target.value;
|
||||
|
||||
if (event.target.nodeName == "input" &&
|
||||
event.target.validity.valid &&
|
||||
event.target.classList.contains("editable")) {
|
||||
let id = event.target.id;
|
||||
if (inputType == "boolean") {
|
||||
if (event.target.checked) {
|
||||
inputValue = true;
|
||||
} else {
|
||||
inputValue = false;
|
||||
}
|
||||
}
|
||||
SaveByType({
|
||||
id: id,
|
||||
type: inputType,
|
||||
value: inputValue
|
||||
});
|
||||
document.getElementById("btn-" + id).classList.remove("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function GenerateField(name, value, hasUserValue, customType, newPreferenceRow) {
|
||||
if (name.length < 1) {
|
||||
return;
|
||||
}
|
||||
let sResetDefault = Strings.GetStringFromName("devicepreferences_reset_default");
|
||||
devicePrefsKeys[name] = true;
|
||||
let input = document.createElement("input");
|
||||
let tr = document.createElement("tr");
|
||||
tr.setAttribute("id", "row-" + name);
|
||||
tr.classList.add("edit-row");
|
||||
let td = document.createElement("td");
|
||||
td.classList.add("preference-name");
|
||||
td.textContent = name;
|
||||
tr.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
input.classList.add("editable");
|
||||
input.setAttribute("id", name);
|
||||
input = RenderByType(input, name, value, customType);
|
||||
if (customType === "boolean" || input.type === "checkbox") {
|
||||
input.checked = value;
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
td.appendChild(input);
|
||||
tr.appendChild(td);
|
||||
td = document.createElement("td");
|
||||
td.setAttribute("id", "td-" + name);
|
||||
|
||||
let button = document.createElement("button");
|
||||
button.setAttribute("data-id", name);
|
||||
button.setAttribute("id", "btn-" + name);
|
||||
button.classList.add("reset");
|
||||
button.textContent = sResetDefault;
|
||||
td.appendChild(button);
|
||||
if (!hasUserValue) {
|
||||
button.classList.add("hide");
|
||||
}
|
||||
tr.appendChild(td);
|
||||
|
||||
// If this is a new preference, add it to the top of the table.
|
||||
if (newPreferenceRow) {
|
||||
let existingPref = table.querySelector("#" + name);
|
||||
if (!existingPref) {
|
||||
table.insertBefore(tr, newPreferenceRow);
|
||||
} else {
|
||||
existingPref.value = value;
|
||||
}
|
||||
} else {
|
||||
table.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function SearchPref(event) {
|
||||
if (event.target.value.length) {
|
||||
let stringMatch = new RegExp(event.target.value, "i");
|
||||
|
||||
for (let key in devicePrefsKeys) {
|
||||
let row = document.getElementById("row-" + key);
|
||||
if (key.match(stringMatch)) {
|
||||
row.classList.remove("hide");
|
||||
} else if (row) {
|
||||
row.classList.add("hide");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var trs = document.getElementsByTagName("tr");
|
||||
|
||||
for (let i = 0; i < trs.length; i++) {
|
||||
trs[i].classList.remove("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function BuildUI() {
|
||||
table = document.querySelector("table");
|
||||
let trs = table.querySelectorAll("tr:not(#add-custom-preference)");
|
||||
|
||||
for (var i = 0; i < trs.length; i++) {
|
||||
table.removeChild(trs[i]);
|
||||
}
|
||||
|
||||
if (AppManager.connection &&
|
||||
AppManager.connection.status == Connection.Status.CONNECTED &&
|
||||
AppManager.preferenceFront) {
|
||||
AppManager.preferenceFront.getAllPrefs().then(json => {
|
||||
let devicePrefs = Object.keys(json);
|
||||
devicePrefs.sort();
|
||||
for (let i = 0; i < devicePrefs.length; i++) {
|
||||
GenerateField(devicePrefs[i], json[devicePrefs[i]].value, json[devicePrefs[i]].hasUserValue);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
CloseUI();
|
||||
}
|
||||
}
|
49
browser/devtools/webide/content/devicepreferences.xhtml
Normal file
49
browser/devtools/webide/content/devicepreferences.xhtml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
|
||||
%webideDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf8"/>
|
||||
<link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://webide/skin/devicepreferences.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://webide/content/devicepreferences.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div id="controls">
|
||||
<a id="close">&deck_close;</a>
|
||||
</div>
|
||||
<h1>&devicepreferences_title;</h1>
|
||||
<div id="search">
|
||||
<input type="text" id="search-bar" placeholder="&devicepreferences_search;"/>
|
||||
</div>
|
||||
</header>
|
||||
<table id="device-preferences">
|
||||
<tr id="add-custom-preference">
|
||||
<td>
|
||||
<select id="custom-value-type">
|
||||
<option value="" selected="selected">&devicepreferences_typenone;</option>
|
||||
<option value="boolean">&devicepreferences_typeboolean;</option>
|
||||
<option value="number">&devicepreferences_typenumber;</option>
|
||||
<option value="string">&devicepreferences_typestring;</option>
|
||||
</select>
|
||||
<input type="text" id="custom-value-name" placeholder="&devicepreferences_newname;"/>
|
||||
</td>
|
||||
<td class="custom-input">
|
||||
<input type="text" id="custom-value-text" placeholder="&devicepreferences_newtext;"/>
|
||||
</td>
|
||||
<td>
|
||||
<button id="custom-value" class="new-editable">&devicepreferences_addnew;</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -20,3 +20,5 @@ webide.jar:
|
||||
content/prefs.xhtml (prefs.xhtml)
|
||||
content/monitor.xhtml (monitor.xhtml)
|
||||
content/monitor.js (monitor.js)
|
||||
content/devicepreferences.js (devicepreferences.js)
|
||||
content/devicepreferences.xhtml (devicepreferences.xhtml)
|
||||
|
@ -765,6 +765,7 @@ let UI = {
|
||||
document.querySelector("#cmd_play").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_stop").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_toggleToolbox").setAttribute("disabled", "true");
|
||||
document.querySelector("#cmd_showDevicePrefs").setAttribute("disabled", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -827,6 +828,7 @@ let UI = {
|
||||
let permissionsCmd = document.querySelector("#cmd_showPermissionsTable");
|
||||
let detailsCmd = document.querySelector("#cmd_showRuntimeDetails");
|
||||
let disconnectCmd = document.querySelector("#cmd_disconnectRuntime");
|
||||
let devicePrefsCmd = document.querySelector("#cmd_showDevicePrefs");
|
||||
|
||||
let box = document.querySelector("#runtime-actions");
|
||||
|
||||
@ -837,6 +839,9 @@ let UI = {
|
||||
permissionsCmd.removeAttribute("disabled");
|
||||
screenshotCmd.removeAttribute("disabled");
|
||||
}
|
||||
if (AppManager.preferenceFront) {
|
||||
devicePrefsCmd.removeAttribute("disabled");
|
||||
}
|
||||
disconnectCmd.removeAttribute("disabled");
|
||||
runtimePanelButton.setAttribute("active", "true");
|
||||
} else {
|
||||
@ -844,6 +849,7 @@ let UI = {
|
||||
permissionsCmd.setAttribute("disabled", "true");
|
||||
screenshotCmd.setAttribute("disabled", "true");
|
||||
disconnectCmd.setAttribute("disabled", "true");
|
||||
devicePrefsCmd.setAttribute("disabled", "true");
|
||||
runtimePanelButton.removeAttribute("active");
|
||||
}
|
||||
|
||||
@ -1181,6 +1187,10 @@ let Cmds = {
|
||||
UI.selectDeckPanel("runtimedetails");
|
||||
},
|
||||
|
||||
showDevicePrefs: function() {
|
||||
UI.selectDeckPanel("devicepreferences");
|
||||
},
|
||||
|
||||
showMonitor: function() {
|
||||
UI.selectDeckPanel("monitor");
|
||||
},
|
||||
|
@ -35,6 +35,7 @@
|
||||
<command id="cmd_newApp" oncommand="Cmds.newApp()" label="&projectMenu_newApp_label;"/>
|
||||
<command id="cmd_importPackagedApp" oncommand="Cmds.importPackagedApp()" label="&projectMenu_importPackagedApp_label;"/>
|
||||
<command id="cmd_importHostedApp" oncommand="Cmds.importHostedApp()" label="&projectMenu_importHostedApp_label;"/>
|
||||
<command id="cmd_showDevicePrefs" label="&runtimeMenu_showDevicePrefs_label;" oncommand="Cmds.showDevicePrefs()"/>
|
||||
<command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
|
||||
<command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
|
||||
<command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
|
||||
@ -78,6 +79,7 @@
|
||||
<menuitem command="cmd_takeScreenshot" accesskey="&runtimeMenu_takeScreenshot_accesskey;"/>
|
||||
<menuitem command="cmd_showPermissionsTable" accesskey="&runtimeMenu_showPermissionTable_accesskey;"/>
|
||||
<menuitem command="cmd_showRuntimeDetails" accesskey="&runtimeMenu_showDetails_accesskey;"/>
|
||||
<menuitem command="cmd_showDevicePrefs" accesskey="&runtimeMenu_showDevicePrefs_accesskey;"/>
|
||||
<menuseparator/>
|
||||
<menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
|
||||
</menupopup>
|
||||
@ -165,6 +167,7 @@
|
||||
<vbox flex="1" id="runtime-actions">
|
||||
<toolbarbutton class="panel-item" id="runtime-details" command="cmd_showRuntimeDetails"/>
|
||||
<toolbarbutton class="panel-item" id="runtime-permissions" command="cmd_showPermissionsTable"/>
|
||||
<toolbarbutton class="panel-item" id="runtime-preferences" command="cmd_showDevicePrefs"/>
|
||||
<toolbarbutton class="panel-item" id="runtime-screenshot" command="cmd_takeScreenshot"/>
|
||||
<toolbarbutton class="panel-item" id="runtime-disconnect" command="cmd_disconnectRuntime"/>
|
||||
</vbox>
|
||||
@ -182,6 +185,7 @@
|
||||
<iframe id="deck-panel-permissionstable" flex="1" src="permissionstable.xhtml"/>
|
||||
<iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
|
||||
<iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
|
||||
<iframe id="deck-panel-devicepreferences" flex="1" src="devicepreferences.xhtml"/>
|
||||
</deck>
|
||||
<splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
|
||||
<!-- toolbox iframe will be inserted here -->
|
||||
|
@ -38,3 +38,4 @@ support-files =
|
||||
[test_deviceinfo.html]
|
||||
[test_autoconnect_runtime.html]
|
||||
[test_telemetry.html]
|
||||
[test_device_preferences.html]
|
||||
|
@ -173,6 +173,25 @@ function removeTab(aTab, aWindow) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function connectToLocalRuntime(aWindow) {
|
||||
info("Loading local runtime.");
|
||||
|
||||
let panelNode = aWindow.document.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-other");
|
||||
is(items.length, 2, "Found 2 custom runtime buttons");
|
||||
|
||||
let deferred = promise.defer();
|
||||
aWindow.AppManager.on("app-manager-update", function onUpdate(e,w) {
|
||||
if (w == "list-tabs-response") {
|
||||
aWindow.AppManager.off("app-manager-update", onUpdate);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
items[1].click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function handleError(aError) {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
finish();
|
||||
|
242
browser/devtools/webide/test/test_device_preferences.html
Normal file
242
browser/devtools/webide/test/test_device_preferences.html
Normal file
@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title></title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(function () { return true; });
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
let prefIframe = win.document.querySelector("#deck-panel-devicepreferences");
|
||||
|
||||
yield documentIsLoaded(prefIframe.contentWindow.document);
|
||||
|
||||
win.AppManager.update("runtimelist");
|
||||
|
||||
yield connectToLocalRuntime(win);
|
||||
|
||||
yield nextTick();
|
||||
|
||||
let prefs = win.document.querySelector("#cmd_showDevicePrefs");
|
||||
|
||||
ok(!prefs.hasAttribute("disabled"), "device prefs cmd enabled");
|
||||
|
||||
let deck = win.document.querySelector("#deck");
|
||||
|
||||
win.Cmds.showDevicePrefs();
|
||||
is(deck.selectedPanel, prefIframe, "device preferences iframe selected");
|
||||
|
||||
yield nextTick();
|
||||
|
||||
let doc = prefIframe.contentWindow.document;
|
||||
let fields = doc.querySelectorAll(".editable");
|
||||
let preference = "accessibility.blockautorefresh";
|
||||
let found = false;
|
||||
|
||||
// Trigger existing field change
|
||||
for (let field of fields) {
|
||||
if (field.id == preference) {
|
||||
let button = doc.getElementById("btn-" + preference);
|
||||
found = true;
|
||||
ok(button.classList.contains("hide"), "Default field detected");
|
||||
field.value = "custom";
|
||||
field.click();
|
||||
ok(!button.classList.contains("hide"), "Custom field detected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ok(found, "Found accessibility preference line");
|
||||
|
||||
// Add new preferences
|
||||
found = false;
|
||||
let customName = doc.querySelector("#custom-value-name");
|
||||
let customValue = doc.querySelector("#custom-value-text");
|
||||
let customValueType = doc.querySelector("#custom-value-type");
|
||||
let customBtn = doc.querySelector("#custom-value");
|
||||
let change = doc.createEvent("HTMLEvents");
|
||||
change.initEvent("change", false, true);
|
||||
|
||||
// Add a new custom string preference
|
||||
customValueType.value = "string";
|
||||
customValueType.dispatchEvent(change);
|
||||
customName.value = "new-string-pref!";
|
||||
customValue.value = "test";
|
||||
customBtn.click();
|
||||
let newPref = doc.querySelector("#new-string-pref");
|
||||
if (newPref) {
|
||||
found = true;
|
||||
is(newPref.type, "text", "Custom type is a string");
|
||||
is(newPref.value, "test", "Custom string new value is correct");
|
||||
}
|
||||
ok(found, "Found new string preference line");
|
||||
is(customName.value, "", "Custom string name reset");
|
||||
is(customValue.value, "", "Custom string value reset");
|
||||
|
||||
// Add a new custom value with the <enter> key
|
||||
found = false;
|
||||
customName.value = "new-string-pref-two";
|
||||
customValue.value = "test";
|
||||
let newField = doc.querySelector("#add-custom-preference");
|
||||
let enter = doc.createEvent("KeyboardEvent");
|
||||
enter.initKeyEvent(
|
||||
"keyup", true, true, null, false, false, false, false, 13, 0);
|
||||
newField.dispatchEvent(enter);
|
||||
newPref = doc.querySelector("#new-string-pref-two");
|
||||
if (newPref) {
|
||||
found = true;
|
||||
is(newPref.type, "text", "Custom type is a string");
|
||||
is(newPref.value, "test", "Custom string new value is correct");
|
||||
}
|
||||
ok(found, "Found new string preference line");
|
||||
is(customName.value, "", "Custom string name reset");
|
||||
is(customValue.value, "", "Custom string value reset");
|
||||
|
||||
// Edit existing custom string preference
|
||||
newPref.value = "test2";
|
||||
newPref.click();
|
||||
is(newPref.value, "test2", "Custom string existing value is correct");
|
||||
|
||||
// Add a new custom integer preference with a valid integer
|
||||
customValueType.value = "number";
|
||||
customValueType.dispatchEvent(change);
|
||||
customName.value = "new-integer-pref";
|
||||
customValue.value = 1;
|
||||
found = false;
|
||||
|
||||
customBtn.click();
|
||||
newPref = doc.querySelector("#new-integer-pref");
|
||||
if (newPref) {
|
||||
found = true;
|
||||
is(newPref.type, "number", "Custom type is a number");
|
||||
is(newPref.value, 1, "Custom integer value is correct");
|
||||
}
|
||||
ok(found, "Found new integer preference line");
|
||||
is(customName.value, "", "Custom integer name reset");
|
||||
is(customValue.value, 0, "Custom integer value reset");
|
||||
|
||||
// Edit existing custom integer preference
|
||||
newPref.value = 3;
|
||||
newPref.click();
|
||||
is(newPref.value, 3, "Custom integer existing value is correct");
|
||||
|
||||
// Reset a custom preference
|
||||
let resetBtn = doc.querySelector("#btn-new-integer-pref");
|
||||
resetBtn.click();
|
||||
|
||||
try {
|
||||
yield prefIframe.contentWindow.defaultPref;
|
||||
} catch(err) {
|
||||
let prefRow = doc.querySelector("#row-new-integer-pref");
|
||||
if (!prefRow) {
|
||||
found = false;
|
||||
}
|
||||
ok(!found, "Custom preference removed");
|
||||
}
|
||||
|
||||
// Reset an existing preference
|
||||
let existingPref = doc.getElementById("accessibility.accesskeycausesactivation");
|
||||
existingPref.click();
|
||||
is(existingPref.checked, false, "Existing boolean value is correct");
|
||||
resetBtn = doc.getElementById("btn-accessibility.accesskeycausesactivation");
|
||||
resetBtn.click();
|
||||
|
||||
yield prefIframe.contentWindow.defaultPref;
|
||||
|
||||
ok(resetBtn.classList.contains("hide"), true, "Reset button hidden");
|
||||
is(existingPref.checked, true, "Existing preference reset");
|
||||
|
||||
// Add a new custom boolean preference
|
||||
customValueType.value = "boolean";
|
||||
customValueType.dispatchEvent(change);
|
||||
customName.value = "new-boolean-pref";
|
||||
customValue.checked = true;
|
||||
found = false;
|
||||
customBtn.click();
|
||||
newPref = doc.querySelector("#new-boolean-pref");
|
||||
if (newPref) {
|
||||
found = true;
|
||||
is(newPref.type, "checkbox", "Custom type is a checkbox");
|
||||
is(newPref.checked, true, "Custom boolean value is correctly true");
|
||||
}
|
||||
ok(found, "Found new boolean preference line");
|
||||
|
||||
// Mouse event trigger
|
||||
var mouseClick = new MouseEvent("click", {
|
||||
canBubble: true,
|
||||
cancelable: true,
|
||||
view: doc.parent,
|
||||
});
|
||||
|
||||
found = false;
|
||||
customValueType.value = "boolean";
|
||||
customValueType.dispatchEvent(change);
|
||||
customName.value = "new-boolean-pref2";
|
||||
customValue.dispatchEvent(mouseClick);
|
||||
customBtn.dispatchEvent(mouseClick);
|
||||
newPref = doc.querySelector("#new-boolean-pref2");
|
||||
if (newPref) {
|
||||
found = true;
|
||||
is(newPref.checked, true, "Custom boolean value is correctly false");
|
||||
}
|
||||
ok(found, "Found new second boolean preference line");
|
||||
|
||||
is(customName.value, "", "Custom boolean name reset");
|
||||
is(customValue.checked, false, "Custom boolean value reset");
|
||||
|
||||
// Edit existing custom boolean preference
|
||||
newPref.click();
|
||||
is(newPref.checked, false, "Custom boolean existing value is correct");
|
||||
|
||||
// Search for a non-existent field
|
||||
let searchField = doc.querySelector("#search-bar");
|
||||
searchField.value = "![o_O]!";
|
||||
searchField.click();
|
||||
|
||||
let preferencesTotal = doc.querySelectorAll("tr.edit-row").length;
|
||||
let hiddenPreferences = doc.querySelectorAll("tr.hide");
|
||||
is(hiddenPreferences.length, preferencesTotal, "Search keyword not found");
|
||||
|
||||
// Search for existing fields
|
||||
searchField.value = "debugger";
|
||||
searchField.click();
|
||||
hiddenPreferences = doc.querySelectorAll("tr.hide");
|
||||
isnot(hiddenPreferences.length, preferencesTotal, "Search keyword found");
|
||||
|
||||
doc.querySelector("#close").click();
|
||||
|
||||
ok(!deck.selectedPanel, "No panel selected");
|
||||
|
||||
DebuggerServer.destroy();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
}).then(null, e => {
|
||||
ok(false, "Exception: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -34,23 +34,7 @@
|
||||
yield documentIsLoaded(permIframe.contentWindow.document);
|
||||
yield documentIsLoaded(infoIframe.contentWindow.document);
|
||||
|
||||
win.AppManager._rebuildRuntimeList();
|
||||
|
||||
let panelNode = win.document.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-other");
|
||||
is(items.length, 2, "Found 2 other runtimes button");
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.on("app-manager-update", function onUpdate(e,w) {
|
||||
if (w == "list-tabs-response") {
|
||||
win.AppManager.off("app-manager-update", onUpdate);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
items[1].click();
|
||||
|
||||
yield deferred.promise;
|
||||
yield connectToLocalRuntime(win);
|
||||
|
||||
yield nextTick();
|
||||
|
||||
|
75
browser/devtools/webide/themes/devicepreferences.css
Normal file
75
browser/devtools/webide/themes/devicepreferences.css
Normal file
@ -0,0 +1,75 @@
|
||||
/* 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/. */
|
||||
|
||||
html, body {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.action[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#device-preferences {
|
||||
font-family: sans-serif;
|
||||
padding-left: 6px;
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
margin-top: 110px;
|
||||
}
|
||||
|
||||
#custom-value-name {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-bottom: 1px solid #EEE;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 90px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
#device-preferences td {
|
||||
background-color: #f1f1f1;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-right: 1px solid #fff;
|
||||
width: 33.3%;
|
||||
}
|
||||
|
||||
#device-preferences td.preference-name {
|
||||
width: 50%;
|
||||
min-width: 400px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#device-preferences button {
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 0.7rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#device-preferences tr.hide, #device-preferences button.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#device-preferences .custom-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#search {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#search-bar {
|
||||
width: 80%;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 34 KiB |
@ -15,3 +15,4 @@ webide.jar:
|
||||
skin/runtimedetails.css (runtimedetails.css)
|
||||
skin/permissionstable.css (permissionstable.css)
|
||||
skin/monitor.css (monitor.css)
|
||||
skin/devicepreferences.css (devicepreferences.css)
|
||||
|
@ -216,6 +216,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
#runtime-details,
|
||||
#runtime-screenshot,
|
||||
#runtime-permissions,
|
||||
#runtime-preferences,
|
||||
#runtime-disconnect,
|
||||
#runtime-panel-nousbdevice,
|
||||
#runtime-panel-noadbhelper,
|
||||
@ -230,6 +231,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
#runtime-details { -moz-image-region: rect(156px,438px,182px,412px) }
|
||||
#runtime-screenshot { -moz-image-region: rect(130px,438px,156px,412px) }
|
||||
#runtime-permissions { -moz-image-region: rect(104px,438px,130px,412px) }
|
||||
#runtime-preferences { -moz-image-region: rect(104px,462px,129px,438px) }
|
||||
#runtime-disconnect { -moz-image-region: rect(52px,438px,78px,412px) }
|
||||
#runtime-panel-nousbdevice { -moz-image-region: rect(156px,438px,182px,412px) }
|
||||
#runtime-panel-noadbhelper { -moz-image-region: rect(234px,438px,260px,412px) }
|
||||
|
@ -268,6 +268,11 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY browserToolboxMenu.label "Browser Toolbox">
|
||||
<!ENTITY browserToolboxMenu.accesskey "e">
|
||||
|
||||
<!-- LOCALIZATION NOTE (browserContentToolboxMenu.label): This is the label for the
|
||||
- application menu item that opens the browser content toolbox UI in the Tools menu.
|
||||
- This toolbox allows to debug the chrome of the content process in multiprocess builds. -->
|
||||
<!ENTITY browserContentToolboxMenu.label "Browser Content Toolbox">
|
||||
|
||||
<!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.label "Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.accesskey "v">
|
||||
|
@ -78,3 +78,8 @@ options.darkTheme.label=Dark theme
|
||||
# LOCALIZATION NOTE (options.lightTheme.label)
|
||||
# Used as a label for light theme
|
||||
options.lightTheme.label=Light theme
|
||||
|
||||
# LOCALIZATION NOTE (toolbox.noContentProcess.message)
|
||||
# Used as a message in the alert displayed when trying to open a browser
|
||||
# content toolbox and there is no content process running
|
||||
toolbox.noContentProcess.message=No content process running.
|
||||
|
@ -39,6 +39,8 @@
|
||||
<!ENTITY runtimeMenu_showDetails_accesskey "E">
|
||||
<!ENTITY runtimeMenu_showMonitor_label "Monitor">
|
||||
<!ENTITY runtimeMenu_showMonitor_accesskey "M">
|
||||
<!ENTITY runtimeMenu_showDevicePrefs_label "Device Preferences">
|
||||
<!ENTITY runtimeMenu_showDevicePrefs_accesskey "D">
|
||||
|
||||
<!ENTITY viewMenu_label "View">
|
||||
<!ENTITY viewMenu_accesskey "V">
|
||||
@ -132,6 +134,17 @@
|
||||
<!ENTITY runtimedetails_requestPrivileges "request higher privileges">
|
||||
<!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
|
||||
|
||||
<!-- Device Preferences -->
|
||||
<!ENTITY devicepreferences_title "Device Preferences">
|
||||
<!ENTITY devicepreferences_search "Search preferences">
|
||||
<!ENTITY devicepreferences_newname "New preference name">
|
||||
<!ENTITY devicepreferences_newtext "Preference value">
|
||||
<!ENTITY devicepreferences_addnew "Add new preference">
|
||||
<!ENTITY devicepreferences_typeboolean "Boolean">
|
||||
<!ENTITY devicepreferences_typenumber "Integer">
|
||||
<!ENTITY devicepreferences_typestring "String">
|
||||
<!ENTITY devicepreferences_typenone "Select a type">
|
||||
|
||||
<!-- Monitor -->
|
||||
<!ENTITY monitor_title "Monitor">
|
||||
<!ENTITY monitor_help "Help">
|
||||
|
@ -72,3 +72,6 @@ status_valid=VALID
|
||||
status_warning=WARNINGS
|
||||
status_error=ERRORS
|
||||
status_unknown=UNKNOWN
|
||||
|
||||
# Preferences
|
||||
devicepreferences_reset_default=Reset to default
|
||||
|
@ -227,6 +227,13 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
||||
background-color: var(--tab-background-color);
|
||||
}
|
||||
|
||||
.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
|
||||
/* The -2px in `calc` is the height of `tabToolbarNavbarOverlap` plus a 1px offset from the center */
|
||||
background-image: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, rgba(29,79,115,0) 70%);
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
|
||||
.tabbrowser-tab:hover {
|
||||
|
@ -68,6 +68,7 @@ import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
|
||||
import org.mozilla.gecko.tiles.TilesRecorder;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
|
||||
import org.mozilla.gecko.toolbar.ToolbarProgressView;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
@ -245,6 +246,8 @@ public class BrowserApp extends GeckoApp
|
||||
// The tab to be selected on editing mode exit.
|
||||
private Integer mTargetTabForEditingMode;
|
||||
|
||||
private final TabEditingState mLastTabEditingState = new TabEditingState();
|
||||
|
||||
// The animator used to toggle HomePager visibility has a race where if the HomePager is shown
|
||||
// (starting the animation), the HomePager is hidden, and the HomePager animation completes,
|
||||
// both the web content and the HomePager will be hidden. This flag is used to prevent the
|
||||
@ -313,17 +316,61 @@ public class BrowserApp extends GeckoApp
|
||||
case BOOKMARK_REMOVED:
|
||||
showBookmarkRemovedToast();
|
||||
break;
|
||||
|
||||
case UNSELECTED:
|
||||
// We receive UNSELECTED immediately after the SELECTED listeners run
|
||||
// so we are ensured that the unselectedTabEditingText has not changed.
|
||||
if (tab.isEditing()) {
|
||||
// Copy to avoid constructing new objects.
|
||||
tab.getEditingState().copyFrom(mLastTabEditingState);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (NewTabletUI.isEnabled(this) && msg == TabEvents.SELECTED) {
|
||||
// Note: this is a duplicate call if maybeSwitchToTab
|
||||
// is called, though the call is idempotent.
|
||||
mBrowserToolbar.cancelEdit();
|
||||
updateEditingModeForTab(tab);
|
||||
}
|
||||
|
||||
super.onTabChanged(tab, msg, data);
|
||||
}
|
||||
|
||||
private void updateEditingModeForTab(final Tab selectedTab) {
|
||||
// (bug 1086983 comment 11) Because the tab may be selected from the gecko thread and we're
|
||||
// running this code on the UI thread, the selected tab argument may not still refer to the
|
||||
// selected tab. However, that means this code should be run again and the initial state
|
||||
// changes will be overridden. As an optimization, we can skip this update, but it may have
|
||||
// unknown side-effects so we don't.
|
||||
if (!Tabs.getInstance().isSelectedTab(selectedTab)) {
|
||||
Log.w(LOGTAG, "updateEditingModeForTab: Given tab is expected to be selected tab");
|
||||
}
|
||||
|
||||
saveTabEditingState(mLastTabEditingState);
|
||||
|
||||
if (selectedTab.isEditing()) {
|
||||
enterEditingMode();
|
||||
restoreTabEditingState(selectedTab.getEditingState());
|
||||
} else {
|
||||
mBrowserToolbar.cancelEdit();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTabEditingState(final TabEditingState editingState) {
|
||||
mBrowserToolbar.saveTabEditingState(editingState);
|
||||
editingState.setIsBrowserSearchShown(mBrowserSearch.getUserVisibleHint());
|
||||
}
|
||||
|
||||
private void restoreTabEditingState(final TabEditingState editingState) {
|
||||
mBrowserToolbar.restoreTabEditingState(editingState);
|
||||
|
||||
// Since changing the editing text will show/hide browser search, this
|
||||
// must be called after we restore the editing state in the edit text View.
|
||||
if (editingState.isBrowserSearchShown()) {
|
||||
showBrowserSearch();
|
||||
} else {
|
||||
hideBrowserSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private void showBookmarkAddedToast() {
|
||||
getButtonToast().show(false,
|
||||
getResources().getString(R.string.bookmark_added),
|
||||
@ -838,6 +885,11 @@ public class BrowserApp extends GeckoApp
|
||||
mBrowserToolbar.setOnStartEditingListener(new BrowserToolbar.OnStartEditingListener() {
|
||||
@Override
|
||||
public void onStartEditing() {
|
||||
final Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
||||
if (selectedTab != null) {
|
||||
selectedTab.setIsEditing(true);
|
||||
}
|
||||
|
||||
// Temporarily disable doorhanger notifications.
|
||||
mDoorHangerPopup.disable();
|
||||
}
|
||||
@ -846,6 +898,11 @@ public class BrowserApp extends GeckoApp
|
||||
mBrowserToolbar.setOnStopEditingListener(new BrowserToolbar.OnStopEditingListener() {
|
||||
@Override
|
||||
public void onStopEditing() {
|
||||
final Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
||||
if (selectedTab != null) {
|
||||
selectedTab.setIsEditing(false);
|
||||
}
|
||||
|
||||
selectTargetTabForEditingMode();
|
||||
|
||||
// Since the underlying LayerView is set visible in hideHomePager, we would
|
||||
@ -1817,6 +1874,11 @@ public class BrowserApp extends GeckoApp
|
||||
return false;
|
||||
}
|
||||
|
||||
final Tab oldTab = tabs.getSelectedTab();
|
||||
if (oldTab != null) {
|
||||
oldTab.setIsEditing(false);
|
||||
}
|
||||
|
||||
// Set the target tab to null so it does not get selected (on editing
|
||||
// mode exit) in lieu of the tab we are about to select.
|
||||
mTargetTabForEditingMode = null;
|
||||
|
@ -7,15 +7,12 @@ package org.mozilla.gecko;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
public class NewTabletUI {
|
||||
// This value should be in sync with preferences_display.xml. On non-release
|
||||
// builds, the preference UI will be hidden and the (unused) default
|
||||
// preference UI value will still be 'true'.
|
||||
private static final boolean DEFAULT = !AppConstants.RELEASE_BUILD;
|
||||
// This value should be in sync with preferences_display.xml.
|
||||
private static final boolean DEFAULT = false;
|
||||
|
||||
private static Boolean sNewTabletUI;
|
||||
|
||||
|
@ -70,7 +70,7 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
||||
}
|
||||
|
||||
case "Reader:ListStatusRequest": {
|
||||
handleReadingListStatusRequest(message.getString("url"));
|
||||
handleReadingListStatusRequest(callback, message.getString("url"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -155,7 +155,7 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
||||
* Gecko (ReaderMode) requests the page ReadingList status, to display
|
||||
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
|
||||
*/
|
||||
private void handleReadingListStatusRequest(final String url) {
|
||||
private void handleReadingListStatusRequest(final EventCallback callback, final String url) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -168,11 +168,10 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
||||
json.put("inReadingList", inReadingList);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(
|
||||
GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
|
||||
// Return the json object to fulfill the promise.
|
||||
callback.sendSuccess(json.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import org.mozilla.gecko.favicons.LoadFaviconTask;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.favicons.RemoteFavicon;
|
||||
import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
@ -77,6 +78,9 @@ public class Tab {
|
||||
private volatile int mRecordingCount;
|
||||
private String mMostRecentHomePanel;
|
||||
|
||||
private boolean mIsEditing;
|
||||
private final TabEditingState mEditingState = new TabEditingState();
|
||||
|
||||
public static final int STATE_DELAYED = 0;
|
||||
public static final int STATE_LOADING = 1;
|
||||
public static final int STATE_SUCCESS = 2;
|
||||
@ -845,4 +849,16 @@ public class Tab {
|
||||
public boolean isRecording() {
|
||||
return mRecordingCount > 0;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return mIsEditing;
|
||||
}
|
||||
|
||||
public void setIsEditing(final boolean isEditing) {
|
||||
this.mIsEditing = isEditing;
|
||||
}
|
||||
|
||||
public TabEditingState getEditingState() {
|
||||
return mEditingState;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
<CheckBoxPreference android:key="android.not_a_preference.new_tablet_ui"
|
||||
android:title="@string/new_tablet_pref"
|
||||
android:defaultValue="true" />
|
||||
android:defaultValue="false" />
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_advanced">
|
||||
|
||||
|
@ -7,6 +7,8 @@ package org.mozilla.gecko.tests;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
|
||||
@ -42,6 +44,8 @@ public class testOSLocale extends BaseTest {
|
||||
private final Object waiter = new Object();
|
||||
|
||||
public void fetch() throws InterruptedException {
|
||||
// Wait for any pending changes to have taken. Bug 1092580.
|
||||
GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createNoOpEvent());
|
||||
synchronized (waiter) {
|
||||
PrefsHelper.getPrefs(TO_FETCH, this);
|
||||
waiter.wait(MAX_WAIT_MS);
|
||||
@ -113,7 +117,6 @@ public class testOSLocale extends BaseTest {
|
||||
// This never changes.
|
||||
final String SELECTED_LOCALES = "es-es,fr,";
|
||||
|
||||
|
||||
// Expected, from es-ES's intl.properties:
|
||||
final String EXPECTED = SELECTED_LOCALES +
|
||||
(isMultiLocaleBuild ? "es,en-us,en" : // Expected, from es-ES's intl.properties.
|
||||
|
@ -422,6 +422,18 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
||||
}
|
||||
}
|
||||
|
||||
public void saveTabEditingState(final TabEditingState editingState) {
|
||||
urlEditLayout.saveTabEditingState(editingState);
|
||||
}
|
||||
|
||||
public void restoreTabEditingState(final TabEditingState editingState) {
|
||||
if (!isEditing()) {
|
||||
throw new IllegalStateException("Expected to be editing");
|
||||
}
|
||||
|
||||
urlEditLayout.restoreTabEditingState(editingState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
||||
Log.d(LOGTAG, "onTabChanged: " + msg);
|
||||
@ -916,4 +928,29 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
||||
setBackgroundResource(R.drawable.url_bar_bg);
|
||||
editCancel.onLightweightThemeReset();
|
||||
}
|
||||
|
||||
public static class TabEditingState {
|
||||
// The edited text from the most recent time this tab was unselected.
|
||||
protected String lastEditingText;
|
||||
protected int selectionStart;
|
||||
protected int selectionEnd;
|
||||
|
||||
public boolean isBrowserSearchShown;
|
||||
|
||||
public void copyFrom(final TabEditingState s2) {
|
||||
lastEditingText = s2.lastEditingText;
|
||||
selectionStart = s2.selectionStart;
|
||||
selectionEnd = s2.selectionEnd;
|
||||
|
||||
isBrowserSearchShown = s2.isBrowserSearchShown;
|
||||
}
|
||||
|
||||
public boolean isBrowserSearchShown() {
|
||||
return isBrowserSearchShown;
|
||||
}
|
||||
|
||||
public void setIsBrowserSearchShown(final boolean isShown) {
|
||||
isBrowserSearchShown = isShown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
|
||||
import org.mozilla.gecko.widget.ThemedLinearLayout;
|
||||
|
||||
import android.content.Context;
|
||||
@ -127,4 +128,15 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
|
||||
String getText() {
|
||||
return mEditText.getText().toString();
|
||||
}
|
||||
|
||||
protected void saveTabEditingState(final TabEditingState editingState) {
|
||||
editingState.lastEditingText = mEditText.getNonAutocompleteText();
|
||||
editingState.selectionStart = mEditText.getSelectionStart();
|
||||
editingState.selectionEnd = mEditText.getSelectionEnd();
|
||||
}
|
||||
|
||||
protected void restoreTabEditingState(final TabEditingState editingState) {
|
||||
mEditText.setText(editingState.lastEditingText);
|
||||
mEditText.setSelection(editingState.selectionStart, editingState.selectionEnd);
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +160,10 @@ public class ToolbarEditText extends CustomEditText
|
||||
setCursorVisible(true);
|
||||
}
|
||||
|
||||
protected String getNonAutocompleteText() {
|
||||
return getNonAutocompleteText(getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the portion of text that is not marked as autocomplete text.
|
||||
*
|
||||
|
@ -35,7 +35,6 @@ let AboutReader = function(doc, win) {
|
||||
Services.obs.addObserver(this, "Reader:FaviconReturn", false);
|
||||
Services.obs.addObserver(this, "Reader:Added", false);
|
||||
Services.obs.addObserver(this, "Reader:Removed", false);
|
||||
Services.obs.addObserver(this, "Reader:ListStatusReturn", false);
|
||||
Services.obs.addObserver(this, "Gesture:DoubleTap", false);
|
||||
|
||||
this._article = null;
|
||||
@ -205,23 +204,6 @@ AboutReader.prototype = {
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:ListStatusReturn": {
|
||||
let args = JSON.parse(aData);
|
||||
if (args.url == this._article.url) {
|
||||
if (this._isReadingListItem != args.inReadingList) {
|
||||
let isInitialStateChange = (this._isReadingListItem == -1);
|
||||
this._isReadingListItem = args.inReadingList;
|
||||
this._updateToggleButton();
|
||||
|
||||
// Display the toolbar when all its initial component states are known
|
||||
if (isInitialStateChange) {
|
||||
this._setToolbarVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Gesture:DoubleTap": {
|
||||
let args = JSON.parse(aData);
|
||||
let scrollBy;
|
||||
@ -275,7 +257,6 @@ AboutReader.prototype = {
|
||||
case "unload":
|
||||
Services.obs.removeObserver(this, "Reader:Added");
|
||||
Services.obs.removeObserver(this, "Reader:Removed");
|
||||
Services.obs.removeObserver(this, "Reader:ListStatusReturn");
|
||||
Services.obs.removeObserver(this, "Gesture:DoubleTap");
|
||||
break;
|
||||
}
|
||||
@ -303,9 +284,23 @@ AboutReader.prototype = {
|
||||
},
|
||||
|
||||
_requestReadingListStatus: function Reader_requestReadingListStatus() {
|
||||
Messaging.sendRequest({
|
||||
Messaging.sendRequestForResult({
|
||||
type: "Reader:ListStatusRequest",
|
||||
url: this._article.url
|
||||
}).then((data) => {
|
||||
let args = JSON.parse(data);
|
||||
if (args.url == this._article.url) {
|
||||
if (this._isReadingListItem != args.inReadingList) {
|
||||
let isInitialStateChange = (this._isReadingListItem == -1);
|
||||
this._isReadingListItem = args.inReadingList;
|
||||
this._updateToggleButton();
|
||||
|
||||
// Display the toolbar when all its initial component states are known
|
||||
if (isInitialStateChange) {
|
||||
this._setToolbarVisibility(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -25,6 +25,7 @@ ContentProcessSingleton.prototype = {
|
||||
case "app-startup": {
|
||||
Services.obs.addObserver(this, "console-api-log-event", false);
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
cpmm.addMessageListener("DevTools:InitDebuggerServer", this);
|
||||
break;
|
||||
}
|
||||
case "console-api-log-event": {
|
||||
@ -56,9 +57,19 @@ ContentProcessSingleton.prototype = {
|
||||
case "xpcom-shutdown":
|
||||
Services.obs.removeObserver(this, "console-api-log-event");
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
cpmm.removeMessageListener("DevTools:InitDebuggerServer", this);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (message) {
|
||||
// load devtools component on-demand
|
||||
// Only reply if we are in a real content process
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
let {init} = Cu.import("resource://gre/modules/devtools/content-server.jsm", {});
|
||||
init(message);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentProcessSingleton]);
|
||||
|
@ -5191,6 +5191,20 @@
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took a 'listAddons' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTPROCESSES_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTPROCESSES_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_LOCAL_DELETE_MS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
/* General utilities used throughout devtools. */
|
||||
|
||||
var { Ci, Cu } = require("chrome");
|
||||
var { Ci, Cu, Cc, components } = require("chrome");
|
||||
var Services = require("Services");
|
||||
var promise = require("promise");
|
||||
var { setTimeout } = require("Timer");
|
||||
@ -407,3 +407,146 @@ exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName,
|
||||
return temp[aSymbol || aName];
|
||||
});
|
||||
};
|
||||
|
||||
exports.defineLazyGetter(this, "NetUtil", () => {
|
||||
return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
|
||||
});
|
||||
|
||||
/**
|
||||
* Performs a request to load the desired URL and returns a promise.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL we will request.
|
||||
* @param aOptions Object
|
||||
* An object with the following optional properties:
|
||||
* - loadFromCache: if false, will bypass the cache and
|
||||
* always load fresh from the network (default: true)
|
||||
* @returns Promise
|
||||
* A promise of the document at that URL, as a string.
|
||||
*
|
||||
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
||||
* without relying on caching when we can (not for eval, etc.):
|
||||
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
||||
*/
|
||||
exports.fetch = function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
let deferred = promise.defer();
|
||||
let scheme;
|
||||
let url = aURL.split(" -> ").pop();
|
||||
let charset;
|
||||
let contentType;
|
||||
|
||||
try {
|
||||
scheme = Services.io.extractScheme(url);
|
||||
} catch (e) {
|
||||
// In the xpcshell tests, the script url is the absolute path of the test
|
||||
// file, which will make a malformed URI error be thrown. Add the file
|
||||
// scheme prefix ourselves.
|
||||
url = "file://" + url;
|
||||
scheme = Services.io.extractScheme(url);
|
||||
}
|
||||
|
||||
dump('scheme: ' + scheme);
|
||||
|
||||
switch (scheme) {
|
||||
case "file":
|
||||
case "chrome":
|
||||
case "resource":
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
|
||||
if (!components.isSuccessCode(aStatus)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatus
|
||||
+ " after NetUtil.asyncFetch for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
|
||||
contentType = aRequest.contentType;
|
||||
deferred.resolve(source);
|
||||
aStream.close();
|
||||
});
|
||||
} catch (ex) {
|
||||
deferred.reject(ex);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
let channel;
|
||||
try {
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
|
||||
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
|
||||
// newChannel won't be able to handle it.
|
||||
url = "file:///" + url;
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
}
|
||||
let chunks = [];
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStartRequest handler for url = "
|
||||
+ url));
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
||||
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStopRequest handler for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
charset = channel.contentCharset;
|
||||
contentType = channel.contentType;
|
||||
deferred.resolve(chunks.join(""));
|
||||
}
|
||||
};
|
||||
|
||||
channel.loadFlags = aOptions.loadFromCache
|
||||
? channel.LOAD_FROM_CACHE
|
||||
: channel.LOAD_BYPASS_CACHE;
|
||||
try {
|
||||
channel.asyncOpen(streamListener, null);
|
||||
} catch(e) {
|
||||
deferred.reject(new Error("Request failed for '"
|
||||
+ url
|
||||
+ "': "
|
||||
+ e.message));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return deferred.promise.then(source => {
|
||||
return {
|
||||
content: convertToUnicode(source, charset),
|
||||
contentType: contentType
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given string, encoded in a given character set, to unicode.
|
||||
*
|
||||
* @param string aString
|
||||
* A string.
|
||||
* @param string aCharset
|
||||
* A character set.
|
||||
*/
|
||||
function convertToUnicode(aString, aCharset=null) {
|
||||
// Decoding primitives.
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
try {
|
||||
converter.charset = aCharset || "UTF-8";
|
||||
return converter.ConvertToUnicode(aString);
|
||||
} catch(e) {
|
||||
return aString;
|
||||
}
|
||||
}
|
||||
|
@ -594,6 +594,21 @@ DebuggerClient.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach to a process in order to get the form of a ChildProcessActor.
|
||||
*
|
||||
* @param string aId
|
||||
* The ID for the process to attach (returned by `listProcesses`).
|
||||
*/
|
||||
attachProcess: function (aId) {
|
||||
let packet = {
|
||||
to: 'root',
|
||||
type: 'attachProcess',
|
||||
id: aId
|
||||
}
|
||||
return this.request(packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an object actor.
|
||||
*
|
||||
@ -1292,6 +1307,15 @@ RootClient.prototype = {
|
||||
listAddons: DebuggerClient.requester({ type: "listAddons" },
|
||||
{ telemetry: "LISTADDONS" }),
|
||||
|
||||
/**
|
||||
* List the running processes.
|
||||
*
|
||||
* @param function aOnResponse
|
||||
* Called with the response packet.
|
||||
*/
|
||||
listProcesses: DebuggerClient.requester({ type: "listProcesses" },
|
||||
{ telemetry: "LISTPROCESSES" }),
|
||||
|
||||
/**
|
||||
* Description of protocol's actors and methods.
|
||||
*
|
||||
|
94
toolkit/devtools/server/actors/child-process.js
Normal file
94
toolkit/devtools/server/actors/child-process.js
Normal file
@ -0,0 +1,94 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
|
||||
const { ChromeDebuggerActor } = require("devtools/server/actors/script");
|
||||
const { WebConsoleActor } = require("devtools/server/actors/webconsole");
|
||||
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
|
||||
const { ActorPool } = require("devtools/server/main");
|
||||
const Services = require("Services");
|
||||
|
||||
function ChildProcessActor(aConnection) {
|
||||
this.conn = aConnection;
|
||||
this._contextPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._contextPool);
|
||||
this._threadActor = null;
|
||||
|
||||
// Use a see-everything debugger
|
||||
this.makeDebugger = makeDebugger.bind(null, {
|
||||
findDebuggees: dbg => dbg.findAllGlobals(),
|
||||
shouldAddNewGlobalAsDebuggee: global => true
|
||||
});
|
||||
|
||||
// Scope into which the webconsole executes:
|
||||
// An empty sandbox with chrome privileges
|
||||
let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
|
||||
.createInstance(Ci.nsIPrincipal);
|
||||
let sandbox = Cu.Sandbox(systemPrincipal);
|
||||
this._consoleScope = sandbox;
|
||||
}
|
||||
exports.ChildProcessActor = ChildProcessActor;
|
||||
|
||||
ChildProcessActor.prototype = {
|
||||
actorPrefix: "process",
|
||||
|
||||
get isRootActor() true,
|
||||
|
||||
get exited() {
|
||||
return !this._contextPool;
|
||||
},
|
||||
|
||||
get url() {
|
||||
return undefined;
|
||||
},
|
||||
|
||||
get window() {
|
||||
return this._consoleScope;
|
||||
},
|
||||
|
||||
form: function() {
|
||||
if (!this._consoleActor) {
|
||||
this._consoleActor = new WebConsoleActor(this.conn, this);
|
||||
this._contextPool.addActor(this._consoleActor);
|
||||
}
|
||||
|
||||
if (!this._threadActor) {
|
||||
this._threadActor = new ChromeDebuggerActor(this.conn, this);
|
||||
this._contextPool.addActor(this._threadActor);
|
||||
}
|
||||
|
||||
return {
|
||||
actor: this.actorID,
|
||||
name: "Content process",
|
||||
|
||||
consoleActor: this._consoleActor.actorID,
|
||||
chromeDebugger: this._threadActor.actorID,
|
||||
|
||||
traits: {
|
||||
highlightable: false,
|
||||
networkMonitor: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
this.conn.removeActorPool(this._contextPool);
|
||||
this._contextPool = null;
|
||||
},
|
||||
|
||||
preNest: function() {
|
||||
// TODO: freeze windows
|
||||
// window mediator doesn't work in child.
|
||||
// it doesn't throw, but doesn't return any window
|
||||
},
|
||||
|
||||
postNest: function() {
|
||||
},
|
||||
};
|
||||
|
||||
ChildProcessActor.prototype.requestTypes = {
|
||||
};
|
@ -6,7 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Ci, Cu } = require("chrome");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
@ -18,6 +18,10 @@ DevToolsUtils.defineLazyGetter(this, "StyleSheetActor", () => {
|
||||
return require("devtools/server/actors/stylesheets").StyleSheetActor;
|
||||
});
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "ppmm", () => {
|
||||
return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
|
||||
});
|
||||
|
||||
/* Root actor for the remote debugging protocol. */
|
||||
|
||||
/**
|
||||
@ -358,6 +362,28 @@ RootActor.prototype = {
|
||||
this._parameters.addonList.onListChanged = null;
|
||||
},
|
||||
|
||||
onListProcesses: function () {
|
||||
let processes = [];
|
||||
for (let i = 0; i < ppmm.childCount; i++) {
|
||||
processes.push({
|
||||
id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything...
|
||||
parent: i == 0, // XXX Weak, but appear to be stable
|
||||
tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this
|
||||
});
|
||||
}
|
||||
return { processes: processes };
|
||||
},
|
||||
|
||||
onAttachProcess: function (aRequest) {
|
||||
let mm = ppmm.getChildAt(aRequest.id);
|
||||
if (!mm) {
|
||||
return { error: "noProcess",
|
||||
message: "There is no process with id '" + aRequest.id + "'." };
|
||||
}
|
||||
return DebuggerServer.connectToContent(this.conn, mm)
|
||||
.then(form => ({ form: form }));
|
||||
},
|
||||
|
||||
/* This is not in the spec, but it's used by tests. */
|
||||
onEcho: function (aRequest) {
|
||||
/*
|
||||
@ -431,6 +457,8 @@ RootActor.prototype = {
|
||||
RootActor.prototype.requestTypes = {
|
||||
"listTabs": RootActor.prototype.onListTabs,
|
||||
"listAddons": RootActor.prototype.onListAddons,
|
||||
"listProcesses": RootActor.prototype.onListProcesses,
|
||||
"attachProcess": RootActor.prototype.onAttachProcess,
|
||||
"echo": RootActor.prototype.onEcho,
|
||||
"protocolDescription": RootActor.prototype.onProtocolDescription
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome");
|
||||
const { ActorPool, getOffsetColumn } = require("devtools/server/actors/common");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dbg_assert, dumpn, update } = DevToolsUtils;
|
||||
const { dbg_assert, dumpn, update, fetch } = DevToolsUtils;
|
||||
const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
|
||||
const promise = require("promise");
|
||||
const PromiseDebugging = require("PromiseDebugging");
|
||||
@ -22,10 +22,6 @@ const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
|
||||
const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
|
||||
const { CssLogic } = require("devtools/styleinspector/css-logic");
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
|
||||
return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
|
||||
});
|
||||
|
||||
let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
|
||||
"Float64Array"];
|
||||
@ -5372,139 +5368,6 @@ function isNotNull(aThing) {
|
||||
return aThing !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request to load the desired URL and returns a promise.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL we will request.
|
||||
* @returns Promise
|
||||
* A promise of the document at that URL, as a string.
|
||||
*
|
||||
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
||||
* without relying on caching when we can (not for eval, etc.):
|
||||
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
||||
*/
|
||||
function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
let deferred = defer();
|
||||
let scheme;
|
||||
let url = aURL.split(" -> ").pop();
|
||||
let charset;
|
||||
let contentType;
|
||||
|
||||
try {
|
||||
scheme = Services.io.extractScheme(url);
|
||||
} catch (e) {
|
||||
// In the xpcshell tests, the script url is the absolute path of the test
|
||||
// file, which will make a malformed URI error be thrown. Add the file
|
||||
// scheme prefix ourselves.
|
||||
url = "file://" + url;
|
||||
scheme = Services.io.extractScheme(url);
|
||||
}
|
||||
|
||||
switch (scheme) {
|
||||
case "file":
|
||||
case "chrome":
|
||||
case "resource":
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
|
||||
if (!components.isSuccessCode(aStatus)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatus
|
||||
+ " after NetUtil.asyncFetch for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
|
||||
contentType = aRequest.contentType;
|
||||
deferred.resolve(source);
|
||||
aStream.close();
|
||||
});
|
||||
} catch (ex) {
|
||||
deferred.reject(ex);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
let channel;
|
||||
try {
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
|
||||
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
|
||||
// newChannel won't be able to handle it.
|
||||
url = "file:///" + url;
|
||||
channel = Services.io.newChannel(url, null, null);
|
||||
}
|
||||
let chunks = [];
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStartRequest handler for url = "
|
||||
+ url));
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
||||
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStopRequest handler for url = "
|
||||
+ url));
|
||||
return;
|
||||
}
|
||||
|
||||
charset = channel.contentCharset;
|
||||
contentType = channel.contentType;
|
||||
deferred.resolve(chunks.join(""));
|
||||
}
|
||||
};
|
||||
|
||||
channel.loadFlags = aOptions.loadFromCache
|
||||
? channel.LOAD_FROM_CACHE
|
||||
: channel.LOAD_BYPASS_CACHE;
|
||||
try {
|
||||
channel.asyncOpen(streamListener, null);
|
||||
} catch(e) {
|
||||
deferred.reject(new Error("Request failed for '"
|
||||
+ url
|
||||
+ "': "
|
||||
+ e.message));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return deferred.promise.then(source => {
|
||||
return {
|
||||
content: convertToUnicode(source, charset),
|
||||
contentType: contentType
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given string, encoded in a given character set, to unicode.
|
||||
*
|
||||
* @param string aString
|
||||
* A string.
|
||||
* @param string aCharset
|
||||
* A character set.
|
||||
*/
|
||||
function convertToUnicode(aString, aCharset=null) {
|
||||
// Decoding primitives.
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
try {
|
||||
converter.charset = aCharset || "UTF-8";
|
||||
return converter.ConvertToUnicode(aString);
|
||||
} catch(e) {
|
||||
return aString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the given error in the error console and to stdout.
|
||||
*
|
||||
|
@ -559,13 +559,15 @@ WebConsoleActor.prototype =
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
case "FileActivity":
|
||||
if (!this.consoleProgressListener) {
|
||||
this.consoleProgressListener =
|
||||
new ConsoleProgressListener(this.window, this);
|
||||
if (this.window instanceof Ci.nsIDOMWindow) {
|
||||
if (!this.consoleProgressListener) {
|
||||
this.consoleProgressListener =
|
||||
new ConsoleProgressListener(this.window, this);
|
||||
}
|
||||
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
|
||||
MONITOR_FILE_ACTIVITY);
|
||||
startedListeners.push(listener);
|
||||
}
|
||||
this.consoleProgressListener.startMonitor(this.consoleProgressListener.
|
||||
MONITOR_FILE_ACTIVITY);
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
case "ReflowActivity":
|
||||
if (!this.consoleReflowListener) {
|
||||
|
64
toolkit/devtools/server/content-server.jsm
Normal file
64
toolkit/devtools/server/content-server.jsm
Normal file
@ -0,0 +1,64 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const { DevToolsLoader } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["init"];
|
||||
|
||||
let started = false;
|
||||
|
||||
function init(msg) {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
started = true;
|
||||
|
||||
// Init a custom, invisible DebuggerServer, in order to not pollute
|
||||
// the debugger with all devtools modules, nor break the debugger itself with using it
|
||||
// in the same process.
|
||||
let devtools = new DevToolsLoader();
|
||||
devtools.invisibleToDebugger = true;
|
||||
devtools.main("devtools/server/main");
|
||||
let { DebuggerServer, ActorPool } = devtools;
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
}
|
||||
|
||||
// In case of apps being loaded in parent process, DebuggerServer is already
|
||||
// initialized, but child specific actors are not registered.
|
||||
// Otherwise, for child process, we need to load actors the first
|
||||
// time we load child.js
|
||||
DebuggerServer.addChildActors();
|
||||
|
||||
let mm = msg.target;
|
||||
mm.QueryInterface(Ci.nsISyncMessageSender);
|
||||
let prefix = msg.data.prefix;
|
||||
|
||||
// Connect both parent/child processes debugger servers RDP via message managers
|
||||
let conn = DebuggerServer.connectToParent(prefix, mm);
|
||||
|
||||
let { ChildProcessActor } = devtools.require("devtools/server/actors/child-process");
|
||||
let actor = new ChildProcessActor(conn);
|
||||
let actorPool = new ActorPool(conn);
|
||||
actorPool.addActor(actor);
|
||||
conn.addActorPool(actorPool);
|
||||
|
||||
let response = {actor: actor.form()};
|
||||
mm.sendAsyncMessage("debug:content-process-actor", response);
|
||||
|
||||
mm.addMessageListener("debug:content-process-destroy", function onDestroy() {
|
||||
mm.removeMessageListener("debug:content-process-destroy", onDestroy);
|
||||
|
||||
DebuggerServer.destroy();
|
||||
started = false;
|
||||
});
|
||||
}
|
@ -704,6 +704,69 @@ var DebuggerServer = {
|
||||
return this._onConnection(transport, aPrefix, true);
|
||||
},
|
||||
|
||||
connectToContent: function (aConnection, aMm) {
|
||||
let deferred = defer();
|
||||
|
||||
let prefix = aConnection.allocID("content-process");
|
||||
let actor, childTransport;
|
||||
|
||||
aMm.addMessageListener("debug:content-process-actor", function listener(msg) {
|
||||
// Arbitrarily choose the first content process to reply
|
||||
// XXX: This code needs to be updated if we use more than one content process
|
||||
aMm.removeMessageListener("debug:content-process-actor", listener);
|
||||
|
||||
// Pipe Debugger message from/to parent/child via the message manager
|
||||
childTransport = new ChildDebuggerTransport(aMm, prefix);
|
||||
childTransport.hooks = {
|
||||
onPacket: aConnection.send.bind(aConnection),
|
||||
onClosed: function () {}
|
||||
};
|
||||
childTransport.ready();
|
||||
|
||||
aConnection.setForwarding(prefix, childTransport);
|
||||
|
||||
dumpn("establishing forwarding for process with prefix " + prefix);
|
||||
|
||||
actor = msg.json.actor;
|
||||
|
||||
deferred.resolve(actor);
|
||||
});
|
||||
|
||||
aMm.sendAsyncMessage("DevTools:InitDebuggerServer", {
|
||||
prefix: prefix
|
||||
});
|
||||
|
||||
function onDisconnect() {
|
||||
Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect");
|
||||
events.off(aConnection, "closed", onDisconnect);
|
||||
if (childTransport) {
|
||||
// If we have a child transport, the actor has already
|
||||
// been created. We need to stop using this message manager.
|
||||
childTransport.close();
|
||||
childTransport = null;
|
||||
aConnection.cancelForwarding(prefix);
|
||||
|
||||
// ... and notify the child process to clean the tab actors.
|
||||
try {
|
||||
aMm.sendAsyncMessage("debug:content-process-destroy");
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
|
||||
if (subject == aMm) {
|
||||
onDisconnect();
|
||||
aConnection.send({ from: actor.actor, type: "tabDetached" });
|
||||
}
|
||||
}).bind(this);
|
||||
Services.obs.addObserver(onMessageManagerDisconnect,
|
||||
"message-manager-disconnect", false);
|
||||
|
||||
events.on(aConnection, "closed", onDisconnect);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect to a child process.
|
||||
*
|
||||
|
@ -21,6 +21,7 @@ SOURCES += [
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
EXTRA_JS_MODULES.devtools += [
|
||||
'content-server.jsm',
|
||||
'dbg-server.jsm',
|
||||
]
|
||||
|
||||
@ -34,6 +35,7 @@ EXTRA_JS_MODULES.devtools.server += [
|
||||
EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
'actors/call-watcher.js',
|
||||
'actors/canvas.js',
|
||||
'actors/child-process.js',
|
||||
'actors/childtab.js',
|
||||
'actors/common.js',
|
||||
'actors/csscoverage.js',
|
||||
|
@ -73,3 +73,5 @@ skip-if = buildapp == 'mulet'
|
||||
[test_preference.html]
|
||||
[test_connectToChild.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_attachProcess.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
@ -0,0 +1,96 @@
|
||||
<SDOCTYPv HTM.>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1060093 - Test DebuggerServer.attachProcess
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mozilla Bug</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
// Always log packets when running tests.
|
||||
["devtools.debugger.log", true],
|
||||
["dom.mozBrowserFramesEnabled", true]
|
||||
]
|
||||
}, runTests);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
// Create a remote iframe with a message manager
|
||||
let iframe = document.createElement("iframe");
|
||||
iframe.mozbrowser = true;
|
||||
iframe.setAttribute("remote", "true");
|
||||
iframe.setAttribute("src", "data:text/html,foo");
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
|
||||
|
||||
// Instantiate a minimal server
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(function () { return true; });
|
||||
}
|
||||
if (!DebuggerServer.createRootActor) {
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
function firstClient() {
|
||||
// Fake a first connection to the content process
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
let client = new DebuggerClient(transport);
|
||||
client.connect(() => {
|
||||
client.mainRoot.listProcesses(response => {
|
||||
ok(response.processes.length >= 2, "Got at least the parent process and one child");
|
||||
|
||||
// Connect to the first content processe available
|
||||
let content = response.processes.filter(p => (!p.parent))[0];
|
||||
|
||||
client.attachProcess(content.id).then(response => {
|
||||
let actor = response.form;
|
||||
ok(actor.consoleActor, "Got the console actor");
|
||||
ok(actor.chromeDebugger, "Got the thread actor");
|
||||
|
||||
// Ensure sending at least one request to an actor...
|
||||
client.request({
|
||||
to: actor.consoleActor,
|
||||
type: "evaluateJS",
|
||||
text: "var a = 42; a"
|
||||
}, function (response) {
|
||||
ok(response.result, 42, "console.eval worked");
|
||||
|
||||
client.close(cleanup);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
DebuggerServer.destroy();
|
||||
iframe.remove();
|
||||
SimpleTest.finish()
|
||||
}
|
||||
|
||||
firstClient();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -4622,7 +4622,7 @@ mozilla::BrowserTabsRemoteAutostart()
|
||||
!Preferences::GetBool("layers.acceleration.force-enabled", false);
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
accelDisabled = !nsCocoaFeatures::AccelerateByDefault();
|
||||
accelDisabled = accelDisabled || !nsCocoaFeatures::AccelerateByDefault();
|
||||
#endif
|
||||
|
||||
// Check for blocked drivers
|
||||
|
Loading…
Reference in New Issue
Block a user