This commit is contained in:
Ryan VanderMeulen 2013-12-06 16:13:37 -05:00
Родитель 2505274530 33a583eacc
Коммит 839040eeee
143 изменённых файлов: 4519 добавлений и 1730 удалений

Просмотреть файл

@ -18,4 +18,4 @@
# Modifying this file will now automatically clobber the buildbot machines \o/
#
Bug 947080 - bug 937317 required clobber on windows (relanding).
Bug 933585 - clobber required on windows

Просмотреть файл

@ -1,4 +1,4 @@
{
"revision": "d10e0357c63bb565db8bdd3c23f062bfb9e21315",
"revision": "63d432c3395f95c0ba19578487b796c1707042bd",
"repo_path": "/integration/gaia-central"
}

Просмотреть файл

@ -869,7 +869,7 @@ let PlacesToolbarHelper = {
return document.getElementById("PlacesToolbar");
},
init: function PTH_init() {
init: function PTH_init(forceToolbarOverflowCheck) {
let viewElt = this._viewElt;
if (!viewElt || viewElt._placesView)
return;
@ -886,6 +886,9 @@ let PlacesToolbarHelper = {
return;
new PlacesToolbar(this._place);
if (forceToolbarOverflowCheck) {
viewElt._placesView.updateOverflowStatus();
}
},
customizeStart: function PTH_customizeStart() {
@ -900,7 +903,7 @@ let PlacesToolbarHelper = {
customizeDone: function PTH_customizeDone() {
this._isCustomizing = false;
this.init();
this.init(true);
},
onPlaceholderCommand: function () {

Просмотреть файл

@ -1037,32 +1037,14 @@ PlacesToolbar.prototype = {
this.updateChevron();
break;
case "overflow":
if (aEvent.target != aEvent.currentTarget)
if (!this._isOverflowStateEventRelevant(aEvent))
return;
// Ignore purely vertical overflows.
if (aEvent.detail == 0)
return;
// Attach the popup binding to the chevron popup if it has not yet
// been initialized.
if (!this._chevronPopup.hasAttribute("type")) {
this._chevronPopup.setAttribute("place", this.place);
this._chevronPopup.setAttribute("type", "places");
}
this._chevron.collapsed = false;
this.updateChevron();
this._onOverflow();
break;
case "underflow":
if (aEvent.target != aEvent.currentTarget)
if (!this._isOverflowStateEventRelevant(aEvent))
return;
// Ignore purely vertical underflows.
if (aEvent.detail == 0)
return;
this.updateChevron();
this._chevron.collapsed = true;
this._onUnderflow();
break;
case "TabOpen":
case "TabClose":
@ -1103,6 +1085,35 @@ PlacesToolbar.prototype = {
}
},
updateOverflowStatus: function() {
if (this._rootElt.scrollLeftMax > 0) {
this._onOverflow();
} else {
this._onUnderflow();
}
},
_isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
// Ignore events not aimed at ourselves, as well as purely vertical ones:
return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
},
_onOverflow: function PT_onOverflow() {
// Attach the popup binding to the chevron popup if it has not yet
// been initialized.
if (!this._chevronPopup.hasAttribute("type")) {
this._chevronPopup.setAttribute("place", this.place);
this._chevronPopup.setAttribute("type", "places");
}
this._chevron.collapsed = false;
this.updateChevron();
},
_onUnderflow: function PT_onUnderflow() {
this.updateChevron();
this._chevron.collapsed = true;
},
updateChevron: function PT_updateChevron() {
// If the chevron is collapsed there's nothing to update.
if (this._chevron.collapsed)

Просмотреть файл

@ -1508,12 +1508,34 @@ let SessionStoreInternal = {
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
},
/**
* Restores the given state |aState| for a given window |aWindow|.
*
* @param aWindow (xul window)
* The window that the given state will be restored to.
* @param aState (string)
* The state that will be applied to the given window.
* @param aOverwrite (bool)
* When true, existing tabs in the given window will be re-used or
* removed. When false, only new tabs will be added, no existing ones
8 will be removed or overwritten.
*/
setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
if (!aWindow.__SSi) {
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
}
this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
let winState = JSON.parse(aState);
if (!winState) {
throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
}
if (!winState.windows || !winState.windows[0]) {
throw Components.Exception("Invalid window state passed", Cr.NS_ERROR_INVALID_ARG);
}
let state = {windows: [winState.windows[0]]};
this.restoreWindow(aWindow, state, {overwriteTabs: aOverwrite});
},
getTabState: function ssi_getTabState(aTab) {
@ -2280,7 +2302,7 @@ let SessionStoreInternal = {
* @param aWindow
* Window reference
* @param aState
* JS object or its eval'able source
* JS object
* @param aOptions
* {overwriteTabs: true} to overwrite existing tabs w/ new ones
* {isFollowUp: true} if this is not the restoration of the 1st window
@ -2300,17 +2322,10 @@ let SessionStoreInternal = {
if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
this.onLoad(aWindow);
try {
var root = typeof aState == "string" ? JSON.parse(aState) : aState;
if (!root.windows[0]) {
this._sendRestoreCompletedNotifications();
return; // nothing to restore
}
}
catch (ex) { // invalid state object - don't restore anything
debug(ex);
var root = aState;
if (!root.windows[0]) {
this._sendRestoreCompletedNotifications();
return;
return; // nothing to restore
}
TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");

Просмотреть файл

@ -78,44 +78,38 @@ function test() {
}
}
// open a window and add the above closed window list
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
newWin.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, false);
gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
test_state._closedWindows.length);
ss.setWindowState(newWin, JSON.stringify(test_state), true);
gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
test_state._closedWindows.length);
ss.setBrowserState(JSON.stringify(test_state), true);
let closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, test_state._closedWindows.length,
"Closed window list has the expected length");
is(countByTitle(closedWindows, FORGET),
test_state._closedWindows.length - remember_count,
"The correct amount of windows are to be forgotten");
is(countByTitle(closedWindows, REMEMBER), remember_count,
"Everything is set up.");
let closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, test_state._closedWindows.length,
"Closed window list has the expected length");
is(countByTitle(closedWindows, FORGET),
test_state._closedWindows.length - remember_count,
"The correct amount of windows are to be forgotten");
is(countByTitle(closedWindows, REMEMBER), remember_count,
"Everything is set up.");
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
ok(testForError(function() ss.forgetClosedWindow(-1)),
"Invalid window for forgetClosedWindow throws");
ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
"Invalid window for forgetClosedWindow throws");
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
ok(testForError(function() ss.forgetClosedWindow(-1)),
"Invalid window for forgetClosedWindow throws");
ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
"Invalid window for forgetClosedWindow throws");
// Remove third window, then first window
ss.forgetClosedWindow(2);
ss.forgetClosedWindow(null);
// Remove third window, then first window
ss.forgetClosedWindow(2);
ss.forgetClosedWindow(null);
closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, remember_count,
"The correct amount of windows were removed");
is(countByTitle(closedWindows, FORGET), 0,
"All windows specifically forgotten were indeed removed");
is(countByTitle(closedWindows, REMEMBER), remember_count,
"... and windows not specifically forgetten weren't.");
closedWindows = JSON.parse(ss.getClosedWindowData());
is(closedWindows.length, remember_count,
"The correct amount of windows were removed");
is(countByTitle(closedWindows, FORGET), 0,
"All windows specifically forgotten were indeed removed");
is(countByTitle(closedWindows, REMEMBER), remember_count,
"... and windows not specifically forgetten weren't.");
// clean up
newWin.close();
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
finish();
}, false);
// clean up
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
finish();
}

Просмотреть файл

@ -686,9 +686,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
// The container is not empty and an actual item was selected.
DebuggerView.setEditorLocation(sourceItem.value);
// Set window title.
let script = sourceItem.value.split(" -> ").pop();
document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
// Set window title. No need to split the url by " -> " here, because it was
// already sanitized when the source was added.
document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", sourceItem.value);
DebuggerView.maybeShowBlackBoxMessage();
this.updateToolbarButtonsState();
@ -1085,38 +1085,14 @@ let SourceUtils = {
try {
// Use an nsIURL to parse all the url path parts.
var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
let url = aUrl.split(" -> ").pop();
var uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
} catch (e) {
// This doesn't look like a url, or nsIURL can't handle it.
return "";
}
let { scheme, directory, fileName } = uri;
let hostPort;
// Add-on SDK jar: URLs will cause accessing hostPort to throw.
if (scheme != "jar") {
hostPort = uri.hostPort;
}
let lastDir = directory.split("/").reverse()[1];
let group = [];
// Only show interesting schemes, http is implicit.
if (scheme != "http") {
group.push(scheme);
}
// Hostnames don't always exist for files or some resource urls.
// e.g. file://foo/bar.js or resource:///foo/bar.js don't have a host.
if (hostPort) {
// If the hostname is a dot-separated identifier, show the first 2 parts.
group.push(hostPort.split(".").slice(0, 2).join("."));
}
// Append the last directory if the path leads to an actual file.
// e.g. http://foo.org/bar/ should only show "foo.org", not "foo.org bar"
if (fileName) {
group.push(lastDir);
}
let groupLabel = group.join(" ");
let groupLabel = uri.prePath;
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
this._groupsCache.set(aUrl, unicodeLabel)
return unicodeLabel;

Просмотреть файл

@ -399,14 +399,6 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
initialize: function() {
dumpn("Initializing the StackFramesView");
let commandset = this._commandset = document.createElement("commandset");
let menupopup = this._menupopup = document.createElement("menupopup");
commandset.id = "stackframesCommandset";
menupopup.id = "stackframesMenupopup";
document.getElementById("debuggerPopupset").appendChild(menupopup);
document.getElementById("debuggerCommands").appendChild(commandset);
this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
this.widget.addEventListener("select", this._onSelect, false);
this.widget.addEventListener("scroll", this._onScroll, true);
@ -414,6 +406,9 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
this.autoFocusOnFirstItem = false;
this.autoFocusOnSelection = false;
// This view's contents are also mirrored in a different container.
this._mirror = DebuggerView.StackFramesClassicList;
},
/**
@ -453,24 +448,22 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
this._prevBlackBoxedUrl = null;
}
// Create the element node and menu entry for the stack frame item.
// Create the element node for the stack frame item.
let frameView = this._createFrameView.apply(this, arguments);
let menuEntry = this._createMenuEntry.apply(this, arguments);
// Append a stack frame item to this container.
this.push([frameView, aTitle, aUrl], {
index: 0, /* specifies on which position should the item be appended */
attachment: {
popup: menuEntry,
depth: aDepth
},
attributes: [
["contextmenu", "stackframesMenupopup"]
],
// Make sure that when the stack frame item is removed, the corresponding
// menuitem and command are also destroyed.
// mirrored item in the classic list is also removed.
finalize: this._onStackframeRemoved
});
// Mirror this newly inserted item inside the "Call Stack" tab.
this._mirror.addFrame(aTitle, aUrl, aLine, aDepth);
},
/**
@ -542,65 +535,6 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
return container;
},
/**
* Customization function for populating an item's context menu.
*
* @param string aTitle
* The frame title to be displayed in the list.
* @param string aUrl
* The frame source url.
* @param string aLine
* The frame line number.
* @param number aDepth
* The frame depth in the stack.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
* @return object
* An object containing the stack frame command and menu item.
*/
_createMenuEntry: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
let frameDescription = SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aUrl),
STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) +
SEARCH_LINE_FLAG + aLine;
let prefix = "sf-cMenu-"; // "stackframes context menu"
let commandId = prefix + aDepth + "-" + "-command";
let menuitemId = prefix + aDepth + "-" + "-menuitem";
let command = document.createElement("command");
command.id = commandId;
command.addEventListener("command", () => this.selectedDepth = aDepth, false);
let menuitem = document.createElement("menuitem");
menuitem.id = menuitemId;
menuitem.className = "dbg-stackframe-menuitem";
menuitem.setAttribute("type", "checkbox");
menuitem.setAttribute("command", commandId);
menuitem.setAttribute("tooltiptext", aUrl);
let labelNode = document.createElement("label");
labelNode.className = "plain dbg-stackframe-menuitem-title";
labelNode.setAttribute("value", aTitle);
labelNode.setAttribute("flex", "1");
let descriptionNode = document.createElement("label");
descriptionNode.className = "plain dbg-stackframe-menuitem-details";
descriptionNode.setAttribute("value", frameDescription);
menuitem.appendChild(labelNode);
menuitem.appendChild(descriptionNode);
this._commandset.appendChild(command);
this._menupopup.appendChild(menuitem);
return {
command: command,
menuitem: menuitem
};
},
/**
* Function called each time a stack frame item is removed.
*
@ -610,10 +544,9 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
_onStackframeRemoved: function(aItem) {
dumpn("Finalizing stackframe item: " + aItem);
// Destroy the context menu item for the stack frame.
let contextItem = aItem.attachment.popup;
contextItem.command.remove();
contextItem.menuitem.remove();
// Remove the mirrored item in the classic list.
let depth = aItem.attachment.depth;
this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth));
// Forget the previously blackboxed stack frame url.
this._prevBlackBoxedUrl = null;
@ -626,17 +559,13 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
let stackframeItem = this.selectedItem;
if (stackframeItem) {
// The container is not empty and an actual item was selected.
DebuggerController.StackFrames.selectFrame(stackframeItem.attachment.depth);
let depth = stackframeItem.attachment.depth;
DebuggerController.StackFrames.selectFrame(depth);
// Update the context menu to show the currently selected stackframe item
// as a checked entry.
for (let otherItem of this) {
if (otherItem != stackframeItem) {
otherItem.attachment.popup.menuitem.removeAttribute("checked");
} else {
otherItem.attachment.popup.menuitem.setAttribute("checked", "");
}
}
// Mirror the selected item in the classic list.
this.suppressSelectionEvents = true;
this._mirror.selectedItem = e => e.attachment.depth == depth;
this.suppressSelectionEvents = false;
}
},
@ -673,11 +602,142 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
}
},
_commandset: null,
_menupopup: null,
_mirror: null,
_prevBlackBoxedUrl: null
});
/*
* Functions handling the stackframes classic list UI.
* Controlled by the DebuggerView.StackFrames isntance.
*/
function StackFramesClassicListView() {
dumpn("StackFramesClassicListView was instantiated");
this._onSelect = this._onSelect.bind(this);
}
StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function() {
dumpn("Initializing the StackFramesClassicListView");
this.widget = new SideMenuWidget(document.getElementById("callstack-list"), {
theme: "light"
});
this.widget.addEventListener("select", this._onSelect, false);
this.emptyText = L10N.getStr("noStackFramesText");
this.autoFocusOnFirstItem = false;
this.autoFocusOnSelection = false;
// This view's contents are also mirrored in a different container.
this._mirror = DebuggerView.StackFrames;
// Show an empty label by default.
this.empty();
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function() {
dumpn("Destroying the StackFramesClassicListView");
this.widget.removeEventListener("select", this._onSelect, false);
},
/**
* Adds a frame in this stackframes container.
*
* @param string aTitle
* The frame title (function name).
* @param string aUrl
* The frame source url.
* @param string aLine
* The frame line number.
* @param number aDepth
* The frame depth in the stack.
*/
addFrame: function(aTitle, aUrl, aLine, aDepth) {
// Create the element node for the stack frame item.
let frameView = this._createFrameView.apply(this, arguments);
// Append a stack frame item to this container.
this.push([frameView, aUrl], {
attachment: {
depth: aDepth
}
});
},
/**
* Customization function for creating an item's UI.
*
* @param string aTitle
* The frame title to be displayed in the list.
* @param string aUrl
* The frame source url.
* @param string aLine
* The frame line number.
* @param number aDepth
* The frame depth in the stack.
* @return nsIDOMNode
* The stack frame view.
*/
_createFrameView: function(aTitle, aUrl, aLine, aDepth) {
let container = document.createElement("hbox");
container.id = "classic-stackframe-" + aDepth;
container.className = "dbg-classic-stackframe";
container.setAttribute("flex", "1");
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-classic-stackframe-title";
frameTitleNode.setAttribute("value", aTitle);
frameTitleNode.setAttribute("crop", "center");
let frameDetailsNode = document.createElement("hbox");
frameDetailsNode.className = "plain dbg-classic-stackframe-details";
let frameUrlNode = document.createElement("label");
frameUrlNode.className = "plain dbg-classic-stackframe-details-url";
frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
frameUrlNode.setAttribute("crop", "center");
frameDetailsNode.appendChild(frameUrlNode);
let frameDetailsSeparator = document.createElement("label");
frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep";
frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG);
frameDetailsNode.appendChild(frameDetailsSeparator);
let frameLineNode = document.createElement("label");
frameLineNode.className = "plain dbg-classic-stackframe-details-line";
frameLineNode.setAttribute("value", aLine);
frameDetailsNode.appendChild(frameLineNode);
container.appendChild(frameTitleNode);
container.appendChild(frameDetailsNode);
return container;
},
/**
* The select listener for the stackframes container.
*/
_onSelect: function(e) {
let stackframeItem = this.selectedItem;
if (stackframeItem) {
// The container is not empty and an actual item was selected.
// Mirror the selected item in the breadcrumbs list.
let depth = stackframeItem.attachment.depth;
this._mirror.selectedItem = e => e.attachment.depth == depth;
}
},
_mirror: null
});
/**
* Functions handling the filtering UI.
*/
@ -1533,3 +1593,4 @@ DebuggerView.FilteredSources = new FilteredSourcesView();
DebuggerView.FilteredFunctions = new FilteredFunctionsView();
DebuggerView.ChromeGlobals = new ChromeGlobalsView();
DebuggerView.StackFrames = new StackFramesView();
DebuggerView.StackFramesClassicList = new StackFramesClassicListView();

Просмотреть файл

@ -9,8 +9,6 @@ const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
const STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH = 32; // chars
const STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION = "center";
const STACK_FRAMES_SCROLL_DELAY = 100; // ms
const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
@ -59,6 +57,7 @@ let DebuggerView = {
this.FilteredFunctions.initialize();
this.ChromeGlobals.initialize();
this.StackFrames.initialize();
this.StackFramesClassicList.initialize();
this.Sources.initialize();
this.VariableBubble.initialize();
this.WatchExpressions.initialize();
@ -93,6 +92,7 @@ let DebuggerView = {
this.FilteredFunctions.destroy();
this.ChromeGlobals.destroy();
this.StackFrames.destroy();
this.StackFramesClassicList.destroy();
this.Sources.destroy();
this.VariableBubble.destroy();
this.WatchExpressions.destroy();
@ -531,7 +531,7 @@ let DebuggerView = {
animated: true,
delayed: true,
callback: aCallback
}, 0);
});
},
/**

Просмотреть файл

@ -4,14 +4,9 @@
* 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/. */
/* Sources search view */
#globalsearch {
overflow: auto;
}
/* Instruments pane view (watch expressions, variables, events...) */
/* Side pane views */
#sources-pane > tabpanels > tabpanel,
#instruments-pane > tabpanels > tabpanel {
-moz-box-orient: vertical;
}
@ -21,6 +16,10 @@
overflow-y: auto;
}
#globalsearch {
overflow: auto;
}
/* Toolbar controls */
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {

Просмотреть файл

@ -321,23 +321,35 @@
<scrollbox id="globalsearch" orient="vertical" hidden="true"/>
<splitter class="devtools-horizontal-splitter" hidden="true"/>
<hbox id="debugger-widgets" flex="1">
<vbox id="sources-pane">
<vbox id="sources" flex="1"/>
<toolbar id="sources-toolbar" class="devtools-toolbar">
<hbox id="sources-controls">
<toolbarbutton id="black-box"
tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
command="blackBoxCommand"
class="devtools-toolbarbutton"/>
<toolbarbutton id="pretty-print"
label="{}"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
hidden="true"/>
</hbox>
</toolbar>
</vbox>
<tabbox id="sources-pane"
class="devtools-sidebar-tabs">
<tabs>
<tab id="sources-tab" label="&debuggerUI.tabs.sources;"/>
<tab id="callstack-tab" label="&debuggerUI.tabs.callstack;"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="sources-tabpanel">
<vbox id="sources" flex="1"/>
<toolbar id="sources-toolbar" class="devtools-toolbar">
<hbox id="sources-controls">
<toolbarbutton id="black-box"
tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
command="blackBoxCommand"
class="devtools-toolbarbutton"/>
<toolbarbutton id="pretty-print"
label="{}"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
hidden="true"/>
</hbox>
</toolbar>
</tabpanel>
<tabpanel id="callstack-tabpanel">
<vbox id="callstack-list" flex="1"/>
</tabpanel>
</tabpanels>
</tabbox>
<splitter id="sources-and-editor-splitter"
class="devtools-side-splitter"/>
<deck id="editor-deck" flex="4">
@ -367,12 +379,12 @@
<tab id="events-tab" label="&debuggerUI.tabs.events;"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="variables-tabpanel">
<tabpanel id="variables-tabpanel" class="theme-body">
<vbox id="expressions"/>
<splitter class="devtools-horizontal-splitter"/>
<vbox id="variables" flex="1"/>
</tabpanel>
<tabpanel id="events-tabpanel">
<tabpanel id="events-tabpanel" class="theme-body">
<vbox id="event-listeners" flex="1"/>
</tabpanel>
</tabpanels>

Просмотреть файл

@ -48,34 +48,40 @@ function test() {
}
function checkNavigationWhileFocused() {
let deferred = promise.defer();
return Task.spawn(function() {
yield promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
EventUtils.sendKey("UP", gDebugger)
]);
checkState({ frame: 2, source: 1, line: 6 });
EventUtils.sendKey("UP", gDebugger);
checkState({ frame: 2, source: 1, line: 6 });
waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
yield promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
waitForSourceAndCaret(gPanel, "-01.js", 5),
EventUtils.sendKey("UP", gDebugger)
]);
checkState({ frame: 1, source: 0, line: 5 });
EventUtils.sendKey("UP", gDebugger);
yield promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
EventUtils.sendKey("UP", gDebugger)
]);
checkState({ frame: 0, source: 0, line: 5 });
waitForSourceAndCaret(gPanel, "-02.js", 6).then(() => {
checkState({ frame: 3, source: 1, line: 6 });
waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
checkState({ frame: 0, source: 0, line: 5 });
deferred.resolve();
});
yield promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
waitForSourceAndCaret(gPanel, "-02.js", 6),
EventUtils.sendKey("END", gDebugger)
]);
checkState({ frame: 3, source: 1, line: 6 });
yield promise.all([
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
waitForSourceAndCaret(gPanel, "-01.js", 5),
EventUtils.sendKey("HOME", gDebugger)
});
EventUtils.sendKey("END", gDebugger)
]);
checkState({ frame: 0, source: 0, line: 5 });
});
EventUtils.sendKey("UP", gDebugger)
return deferred.promise;
}
function checkState({ frame, source, line }) {

Просмотреть файл

@ -46,7 +46,7 @@ function performTest() {
is(gSources.widget.getAttribute("label"), "doc_recursion-stack.html",
"The sources widget should have a correct label attribute.");
is(gSources.widget.getAttribute("tooltiptext"), "example.com test",
is(gSources.widget.getAttribute("tooltiptext"), "http://example.com",
"The sources widget should have a correct tooltip text attribute.");
is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0,

Просмотреть файл

@ -48,7 +48,7 @@ function testLocationChange() {
is(gSources.widget.getAttribute("label"), "doc_inline-debugger-statement.html",
"The sources widget should have a correct label attribute.");
is(gSources.widget.getAttribute("tooltiptext"), "example.com test",
is(gSources.widget.getAttribute("tooltiptext"), "http://example.com",
"The sources widget should have a correct tooltip text attribute.");
is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0,

Просмотреть файл

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gFrames;
let gFrames, gClassicFrames;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -17,6 +17,7 @@ function test() {
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, ".html", 14).then(performTest);
gDebuggee.simpleCall();
@ -28,6 +29,8 @@ function performTest() {
"Should only be getting stack frames while paused.");
is(gFrames.itemCount, 1,
"Should have only one frame.");
is(gClassicFrames.itemCount, 1,
"Should also have only one frame in the mirrored view.");
resumeDebuggerThenCloseAndFinish(gPanel);
}
@ -38,4 +41,5 @@ registerCleanupFunction(function() {
gPanel = null;
gDebugger = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gFrames;
let gFrames, gClassicFrames;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -17,6 +17,7 @@ function test() {
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, ".html", 18).then(performTest);
gDebuggee.evalCall();
@ -28,21 +29,32 @@ function performTest() {
"Should only be getting stack frames while paused.");
is(gFrames.itemCount, 2,
"Should have two frames.");
is(gClassicFrames.itemCount, 2,
"Should also have only two in the mirrored view.");
is(gFrames.getItemAtIndex(0).value,
"evalCall", "Oldest frame name should be correct.");
is(gFrames.getItemAtIndex(0).description,
TAB_URL, "Oldest frame url should be correct.");
is(gClassicFrames.getItemAtIndex(0).value,
TAB_URL, "Oldest frame name is mirrored correctly.");
is(gFrames.getItemAtIndex(1).value,
"(eval)", "Newest frame name should be correct.");
is(gFrames.getItemAtIndex(1).description,
TAB_URL, "Newest frame url should be correct.");
is(gClassicFrames.getItemAtIndex(1).value,
TAB_URL, "Newest frame name is mirrored correctly.");
is(gFrames.selectedIndex, 1,
"Newest frame should be selected by default.");
is(gClassicFrames.selectedIndex, 0,
"Newest frame should be selected by default in the mirrored view.");
isnot(gFrames.selectedIndex, 0,
"Oldest frame should not be selected.");
isnot(gClassicFrames.selectedIndex, 1,
"Oldest frame should not be selected in the mirrored view.");
EventUtils.sendMouseEvent({ type: "mousedown" },
gFrames.getItemAtIndex(0).target,
@ -50,8 +62,13 @@ function performTest() {
isnot(gFrames.selectedIndex, 1,
"Newest frame should not be selected after click.");
isnot(gClassicFrames.selectedIndex, 0,
"Newest frame in the mirrored view should not be selected.");
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click.");
is(gClassicFrames.selectedIndex, 1,
"Oldest frame in the mirrored view should be selected.");
EventUtils.sendMouseEvent({ type: "mousedown" },
gFrames.getItemAtIndex(1).target.querySelector(".dbg-stackframe-title"),
@ -59,8 +76,13 @@ function performTest() {
is(gFrames.selectedIndex, 1,
"Newest frame should be selected after click inside the newest frame.");
is(gClassicFrames.selectedIndex, 0,
"Newest frame in the mirrored view should be selected.");
isnot(gFrames.selectedIndex, 0,
"Oldest frame should not be selected after click inside the newest frame.");
isnot(gClassicFrames.selectedIndex, 1,
"Oldest frame in the mirrored view should not be selected.");
EventUtils.sendMouseEvent({ type: "mousedown" },
gFrames.getItemAtIndex(0).target.querySelector(".dbg-stackframe-details"),
@ -68,8 +90,13 @@ function performTest() {
isnot(gFrames.selectedIndex, 1,
"Newest frame should not be selected after click inside the oldest frame.");
isnot(gClassicFrames.selectedIndex, 0,
"Newest frame in the mirrored view should not be selected.");
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click inside the oldest frame.");
is(gClassicFrames.selectedIndex, 1,
"Oldest frame in the mirrored view should be selected.");
resumeDebuggerThenCloseAndFinish(gPanel);
}
@ -80,4 +107,5 @@ registerCleanupFunction(function() {
gPanel = null;
gDebugger = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gFrames, gFramesScrollingInterval;
let gFrames, gClassicFrames, gFramesScrollingInterval;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -17,6 +17,7 @@ function test() {
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, ".html", 26).then(performTest);
@ -30,14 +31,20 @@ function performTest() {
"Should only be getting stack frames while paused.");
is(gFrames.itemCount, gDebugger.gCallStackPageSize,
"Should have only the max limit of frames.");
is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize,
"Should have only the max limit of frames in the mirrored view as well.")
gDebugger.gThreadClient.addOneTimeListener("framesadded", () => {
is(gFrames.itemCount, gDebugger.gCallStackPageSize * 2,
"Should now have twice the max limit of frames.");
is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize * 2,
"Should now have twice the max limit of frames in the mirrored view as well.");
gDebugger.gThreadClient.addOneTimeListener("framesadded", () => {
is(gFrames.itemCount, gDebuggee.gRecurseLimit,
"Should have reached the recurse limit.");
is(gClassicFrames.itemCount, gDebuggee.gRecurseLimit,
"Should have reached the recurse limit in the mirrored view as well.");
gDebugger.gThreadClient.resume(() => {
window.clearInterval(gFramesScrollingInterval);
@ -60,4 +67,5 @@ registerCleanupFunction(function() {
gPanel = null;
gDebugger = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gFrames;
let gFrames, gClassicFrames;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -17,6 +17,7 @@ function test() {
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, ".html", 18).then(performTest);
gDebuggee.evalCall();
@ -28,10 +29,14 @@ function performTest() {
"Should only be getting stack frames while paused.");
is(gFrames.itemCount, 2,
"Should have two frames.");
is(gClassicFrames.itemCount, 2,
"Should also have two frames in the mirrored view.");
gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
is(gFrames.itemCount, 0,
"Should have no frames after resume.");
is(gClassicFrames.itemCount, 0,
"Should also have no frames in the mirrored view after resume.");
closeDebuggerAndFinish(gPanel);
}, true);
@ -45,4 +50,5 @@ registerCleanupFunction(function() {
gPanel = null;
gDebugger = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -9,7 +9,7 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gFrames;
let gEditor, gSources, gFrames, gClassicFrames;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -20,6 +20,7 @@ function test() {
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
.then(initialChecks)
@ -40,6 +41,8 @@ function initialChecks() {
"Should only be getting stack frames while paused.");
is(gFrames.itemCount, 4,
"Should have four frames.");
is(gClassicFrames.itemCount, 4,
"Should also have four frames in the mirrored view.");
}
function testNewestTwoFrames() {
@ -47,6 +50,8 @@ function testNewestTwoFrames() {
is(gFrames.selectedIndex, 3,
"Newest frame should be selected by default.");
is(gClassicFrames.selectedIndex, 0,
"Newest frame should be selected in the mirrored view as well.");
is(gSources.selectedIndex, 1,
"The second source is selected in the widget.");
ok(isCaretPos(gPanel, 6),
@ -63,6 +68,8 @@ function testNewestTwoFrames() {
is(gFrames.selectedIndex, 2,
"Third frame should be selected after click.");
is(gClassicFrames.selectedIndex, 1,
"Third frame should be selected in the mirrored view as well.");
is(gSources.selectedIndex, 1,
"The second source is still selected in the widget.");
ok(isCaretPos(gPanel, 6),
@ -83,9 +90,11 @@ function testNewestTwoFrames() {
function testOldestTwoFrames() {
let deferred = promise.defer();
waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
waitForSourceAndCaret(gPanel, "-01.js", 5).then(waitForTick).then(() => {
is(gFrames.selectedIndex, 1,
"Second frame should be selected after click.");
is(gClassicFrames.selectedIndex, 2,
"Second frame should be selected in the mirrored view as well.");
is(gSources.selectedIndex, 0,
"The first source is now selected in the widget.");
ok(isCaretPos(gPanel, 5),
@ -102,6 +111,8 @@ function testOldestTwoFrames() {
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click.");
is(gClassicFrames.selectedIndex, 3,
"Oldest frame should be selected in the mirrored view as well.");
is(gSources.selectedIndex, 0,
"The first source is still selected in the widget.");
ok(isCaretPos(gPanel, 5),
@ -118,7 +129,7 @@ function testOldestTwoFrames() {
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gFrames.getItemAtIndex(1).target,
gDebugger.document.querySelector("#stackframe-2"),
gDebugger);
return deferred.promise;
@ -130,6 +141,8 @@ function testAfterResume() {
gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
is(gFrames.itemCount, 0,
"Should have no frames after resume.");
is(gClassicFrames.itemCount, 0,
"Should have no frames in the mirrored view as well.");
ok(isCaretPos(gPanel, 5),
"Editor caret location is correct after resume.");
is(gEditor.getDebugLocation(), null,
@ -150,5 +163,6 @@ registerCleanupFunction(function() {
gDebugger = null;
gEditor = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -9,7 +9,7 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gFrames;
let gEditor, gSources, gFrames, gClassicFrames;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -20,6 +20,7 @@ function test() {
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
gDebuggee.firstCall();
@ -29,16 +30,20 @@ function test() {
function performTest() {
is(gFrames.selectedIndex, 3,
"Newest frame should be selected by default.");
is(gClassicFrames.selectedIndex, 0,
"Newest frame should also be selected in the mirrored view.");
is(gSources.selectedIndex, 1,
"The second source is selected in the widget.");
is(gEditor.getText().search(/firstCall/), -1,
"The first source is not displayed.");
is(gEditor.getText().search(/debugger/), 172,
"The second source is displayed.")
"The second source is displayed.");
waitForSourceAndCaret(gPanel, "-01.js", 6).then(() => {
waitForSourceAndCaret(gPanel, "-01.js", 6).then(waitForTick).then(() => {
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click.");
is(gClassicFrames.selectedIndex, 3,
"Oldest frame should also be selected in the mirrored view.");
is(gSources.selectedIndex, 0,
"The first source is now selected in the widget.");
is(gEditor.getText().search(/firstCall/), 118,
@ -46,7 +51,24 @@ function performTest() {
is(gEditor.getText().search(/debugger/), -1,
"The second source is not displayed.");
resumeDebuggerThenCloseAndFinish(gPanel);
waitForSourceAndCaret(gPanel, "-02.js", 6).then(waitForTick).then(() => {
is(gFrames.selectedIndex, 3,
"Newest frame should be selected again after click.");
is(gClassicFrames.selectedIndex, 0,
"Newest frame should also be selected again in the mirrored view.");
is(gSources.selectedIndex, 1,
"The second source is selected in the widget.");
is(gEditor.getText().search(/firstCall/), -1,
"The first source is not displayed.");
is(gEditor.getText().search(/debugger/), 172,
"The second source is displayed.");
resumeDebuggerThenCloseAndFinish(gPanel);
});
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.querySelector("#classic-stackframe-0"),
gDebugger);
});
EventUtils.sendMouseEvent({ type: "mousedown" },
@ -62,4 +84,5 @@ registerCleanupFunction(function() {
gEditor = null;
gSources = null;
gFrames = null;
gClassicFrames = null;
});

Просмотреть файл

@ -10,7 +10,7 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gFrames, gToolbar;
let gEditor, gSources, gFrames, gClassicFrames, gToolbar;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -21,6 +21,7 @@ function test() {
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gFrames = gDebugger.DebuggerView.StackFrames;
gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
gToolbar = gDebugger.DebuggerView.Toolbar;
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
@ -53,13 +54,15 @@ function performTest() {
function selectBottomFrame() {
let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
gFrames.selectedIndex = 0;
gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1;
return updated.then(waitForTick);
}
function testBottomFrame(debugLocation) {
is(gFrames.selectedIndex, 0,
"Oldest frame should be selected after click.");
is(gClassicFrames.selectedIndex, gFrames.itemCount - 1,
"Oldest frame should also be selected in the mirrored view.");
is(gSources.selectedIndex, 0,
"The first source is now selected in the widget.");
is(gEditor.getText().search(/firstCall/), 118,
@ -82,6 +85,8 @@ function performTest() {
function testTopFrame(frameIndex) {
is(gFrames.selectedIndex, frameIndex,
"Topmost frame should be selected after click.");
is(gClassicFrames.selectedIndex, gFrames.itemCount - frameIndex - 1,
"Topmost frame should also be selected in the mirrored view.");
is(gSources.selectedIndex, 1,
"The second source is now selected in the widget.");
is(gEditor.getText().search(/firstCall/), -1,
@ -99,5 +104,6 @@ registerCleanupFunction(function() {
gEditor = null;
gSources = null;
gFrames = null;
gClassicFrames = null;
gToolbar = null;
});

Просмотреть файл

@ -6,7 +6,7 @@
"use strict";
const { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("sdk/core/promise");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const EventEmitter = require("devtools/shared/event-emitter");
const { WebGLFront } = require("devtools/server/actors/webgl");

Просмотреть файл

@ -13,7 +13,7 @@ Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const promise = require("sdk/core/promise");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const EventEmitter = require("devtools/shared/event-emitter");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
const Editor = require("devtools/sourceeditor/editor");
@ -28,7 +28,13 @@ const EVENTS = {
SOURCES_SHOWN: "ShaderEditor:SourcesShown",
// When a shader's source was edited and compiled via the editor.
SHADER_COMPILED: "ShaderEditor:ShaderCompiled"
SHADER_COMPILED: "ShaderEditor:ShaderCompiled",
// When the UI is reset from tab navigation
UI_RESET: "ShaderEditor:UIReset",
// When the editor's error markers are all removed
EDITOR_ERROR_MARKERS_REMOVED: "ShaderEditor:EditorCleaned"
};
const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
@ -114,15 +120,17 @@ let EventsHandler = {
_onTabNavigated: function(event) {
switch (event) {
case "will-navigate": {
// Make sure the backend is prepared to handle WebGL contexts.
gFront.setup({ reload: false });
Task.spawn(function() {
// Make sure the backend is prepared to handle WebGL contexts.
gFront.setup({ reload: false });
// Reset UI.
ShadersListView.empty();
ShadersEditorsView.setText({ vs: "", fs: "" });
$("#reload-notice").hidden = true;
$("#waiting-notice").hidden = false;
$("#content").hidden = true;
// Reset UI.
ShadersListView.empty();
$("#reload-notice").hidden = true;
$("#waiting-notice").hidden = false;
yield ShadersEditorsView.setText({ vs: "", fs: "" });
$("#content").hidden = true;
}).then(() => window.emit(EVENTS.UI_RESET));
break;
}
case "navigate": {
@ -272,13 +280,16 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
]);
}
function showSources([vertexShaderText, fragmentShaderText]) {
ShadersEditorsView.setText({
return ShadersEditorsView.setText({
vs: vertexShaderText,
fs: fragmentShaderText
});
}
getShaders().then(getSources).then(showSources).then(null, Cu.reportError);
getShaders()
.then(getSources)
.then(showSources)
.then(null, Cu.reportError);
},
/**
@ -351,19 +362,24 @@ let ShadersEditorsView = {
* An object containing the following properties
* - vs: the vertex shader source code
* - fs: the fragment shader source code
* @return object
* A promise resolving upon completion of text setting.
*/
setText: function(sources) {
let view = this;
function setTextAndClearHistory(editor, text) {
editor.setText(text);
editor.clearHistory();
}
this._toggleListeners("off");
this._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs));
this._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs));
this._toggleListeners("on");
window.emit(EVENTS.SOURCES_SHOWN, sources);
return Task.spawn(function() {
yield view._toggleListeners("off");
yield promise.all([
view._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs)),
view._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs))
]);
yield view._toggleListeners("on");
}).then(() => window.emit(EVENTS.SOURCES_SHOWN, sources));
},
/**
@ -372,10 +388,12 @@ let ShadersEditorsView = {
* @param string type
* Specifies for which shader type should an editor be retrieved,
* either are "vs" for a vertex, or "fs" for a fragment shader.
* @return object
* Returns a promise that resolves to an editor instance
*/
_getEditor: function(type) {
if ($("#content").hidden) {
return promise.reject(null);
return promise.reject(new Error("Shader Editor is still waiting for a WebGL context to be created."));
}
if (this._editorPromises.has(type)) {
return this._editorPromises.get(type);
@ -399,14 +417,16 @@ let ShadersEditorsView = {
*
* @param string flag
* Either "on" to enable the event listeners, "off" to disable them.
* @return object
* A promise resolving upon completion of toggling the listeners.
*/
_toggleListeners: function(flag) {
["vs", "fs"].forEach(type => {
this._getEditor(type).then(editor => {
return promise.all(["vs", "fs"].map(type => {
return this._getEditor(type).then(editor => {
editor[flag]("focus", this["_" + type + "Focused"]);
editor[flag]("change", this["_" + type + "Changed"]);
});
});
}));
},
/**
@ -486,7 +506,7 @@ let ShadersEditorsView = {
}
function sanitizeValidMatches(e) {
return {
// Drivers might yield retarded line numbers under some obscure
// Drivers might yield confusing line numbers under some obscure
// circumstances. Don't throw the errors away in those cases,
// just display them on the currently edited line.
line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
@ -554,6 +574,7 @@ let ShadersEditorsView = {
editor.removeAllMarkers("errors");
this._errors[type].forEach(e => editor.removeLineClass(e.line));
this._errors[type].length = 0;
window.emit(EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
});
},

Просмотреть файл

@ -14,8 +14,7 @@ function ifWebGLSupported() {
yield reloaded;
let navigated = navigate(target, MULTIPLE_CONTEXTS_URL);
let secondProgram = yield once(gFront, "program-linked");
let thirdProgram = yield once(gFront, "program-linked");
let [secondProgram, thirdProgram] = yield getPrograms(gFront, 2);
yield navigated;
let vsEditor = yield ShadersEditorsView._getEditor("vs");
@ -56,9 +55,3 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -8,14 +8,18 @@
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, ShadersEditorsView } = panel.panelWin;
let { gFront, ShadersEditorsView, EVENTS } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
is(vsEditor.getText().indexOf("gl_Position"), 170,
"The vertex shader editor contains the correct text.");
is(fsEditor.getText().indexOf("gl_FragColor"), 97,

Просмотреть файл

@ -11,55 +11,61 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks added in the vertex shader editor.");
vsEditor.insertText(" ", { line: 1, ch: 0 });
yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
is(vsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks removed while typing in the vertex shader editor.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
[, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks were re-added after recompiling the vertex shader.");
fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks added in the fragment shader editor.");
fsEditor.insertText(" ", { line: 1, ch: 0 });
yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
is(fsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(false, fragError);
info("Error marks removed while typing in the fragment shader editor.");
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
[, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks were re-added after recompiling the fragment shader.");
vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks removed while typing in the vertex shader editor again.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
[, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(true, vertError);
checkHasFragError(true, fragError);
@ -148,9 +154,3 @@ function ifWebGLSupported() {
}
}
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

Просмотреть файл

@ -11,7 +11,10 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
@ -51,9 +54,3 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

Просмотреть файл

@ -7,10 +7,13 @@
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, $, ShadersListView, ShadersEditorsView } = panel.panelWin;
let { gFront, $, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
is($("#reload-notice").hidden, true,
"The 'reload this page' notice should be hidden after linking.");
@ -39,6 +42,7 @@ function ifWebGLSupported() {
navigate(target, "about:blank");
yield navigating;
yield once(panel.panelWin, EVENTS.UI_RESET);
is($("#reload-notice").hidden, true,
"The 'reload this page' notice should be hidden while navigating.");
@ -54,19 +58,17 @@ function ifWebGLSupported() {
is(ShadersListView.selectedIndex, -1,
"The shaders list has a negative index.");
try {
yield ShadersEditorsView._getEditor("vs");
yield ShadersEditorsView._getEditor("vs").then(() => {
ok(false, "The promise for a vertex shader editor should be rejected.");
} catch (e) {
}, () => {
ok(true, "The vertex shader editors wasn't initialized.");
}
});
try {
yield ShadersEditorsView._getEditor("fs");
yield ShadersEditorsView._getEditor("fs").then(() => {
ok(false, "The promise for a fragment shader editor should be rejected.");
} catch (e) {
}, () => {
ok(true, "The fragment shader editors wasn't initialized.");
}
});
yield navigated;

Просмотреть файл

@ -14,8 +14,10 @@ function ifWebGLSupported() {
});
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
let secondProgramActor = yield once(gFront, "program-linked");
let [firstProgramActor, secondProgramActor] = yield promise.all([
getPrograms(gFront, 2),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs]) => programs);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");

Просмотреть файл

@ -11,8 +11,10 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
let secondProgramActor = yield once(gFront, "program-linked");
let [firstProgramActor, secondProgramActor] = yield promise.all([
getPrograms(gFront, 2),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs]) => programs);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
@ -59,9 +61,3 @@ function getBlackBoxCheckbox(aPanel, aIndex) {
return aPanel.panelWin.document.querySelectorAll(
".side-menu-widget-item-checkbox")[aIndex];
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -7,10 +7,14 @@
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
let { gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
let { EVENTS, gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
reload(target);
let programActor = yield once(gFront, "program-linked");
let [programActor] = yield promise.all([
getPrograms(gFront, 1),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs]) => programs);
let programItem = ShadersListView.selectedItem;
is(programItem.attachment.programActor, programActor,

Просмотреть файл

@ -14,8 +14,10 @@ function ifWebGLSupported() {
});
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
let secondProgramActor = yield once(gFront, "program-linked");
let [firstProgramActor, secondProgramActor] = yield promise.all([
getPrograms(gFront, 2),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs]) => programs);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
@ -89,9 +91,3 @@ function getBlackBoxCheckbox(aPanel, aIndex) {
return aPanel.panelWin.document.querySelectorAll(
".side-menu-widget-item-checkbox")[aIndex];
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -11,8 +11,10 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
let secondProgramActor = yield once(gFront, "program-linked");
let [firstProgramActor, secondProgramActor] = yield promise.all([
getPrograms(gFront, 2),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs]) => programs);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
@ -45,9 +47,3 @@ function getItemLabel(aPanel, aIndex) {
return aPanel.panelWin.document.querySelectorAll(
".side-menu-widget-item-label")[aIndex];
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -19,23 +19,17 @@ function ifWebGLSupported() {
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
is(ShadersListView.itemCount, 1,
"The shaders list contains one entry.");
is(ShadersListView.selectedItem, ShadersListView.items[0],
"The shaders list has a correct item selected.");
is(ShadersListView.selectedIndex, 0,
"The shaders list has a correct index selected.");
let secondProgramActor = yield once(gFront, "program-linked");
is(ShadersListView.itemCount, 2,
"The shaders list contains two entries.");
is(ShadersListView.selectedItem, ShadersListView.items[0],
"The shaders list has a correct item selected.");
is(ShadersListView.selectedIndex, 0,
"The shaders list has a correct index selected.");
let [firstProgramActor, secondProgramActor] = yield promise.all([
getPrograms(gFront, 2, (actors) => {
// Fired upon each actor addition, we want to check only
// after the first actor has been added so we can test state
if (actors.length === 1)
checkFirstProgram();
if (actors.length === 2)
checkSecondProgram();
}),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]).then(([programs, ]) => programs);
is(ShadersListView.labels[0], L10N.getFormatStr("shadersList.programLabel", 0),
"The correct first label is shown in the shaders list.");
@ -73,10 +67,21 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
function checkFirstProgram () {
is(ShadersListView.itemCount, 1,
"The shaders list contains one entry.");
is(ShadersListView.selectedItem, ShadersListView.items[0],
"The shaders list has a correct item selected.");
is(ShadersListView.selectedIndex, 0,
"The shaders list has a correct index selected.");
}
function checkSecondProgram () {
is(ShadersListView.itemCount, 2,
"The shaders list contains two entries.");
is(ShadersListView.selectedItem, ShadersListView.items[0],
"The shaders list has a correct item selected.");
is(ShadersListView.selectedIndex, 0,
"The shaders list has a correct index selected.");
}
}

Просмотреть файл

@ -10,7 +10,10 @@ function ifWebGLSupported() {
let { gFront, $, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
@ -68,9 +71,3 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -11,13 +11,16 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(panel.panelWin, EVENTS.SOURCES_SHOWN)
]);
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
ok(error,
"The new vertex shader source was compiled with errors.");
@ -33,7 +36,7 @@ function ifWebGLSupported() {
"An assignment error is contained in the linkage status.");
fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
ok(error,
"The new fragment shader source was compiled with errors.");
@ -50,11 +53,11 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
vsEditor.replaceText("vec4", { line: 7, ch: 22 }, { line: 7, ch: 26 });
let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
ok(!error, "The new vertex shader source was compiled successfully.");
fsEditor.replaceText("vec3", { line: 2, ch: 14 }, { line: 2, ch: 18 });
let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
ok(!error, "The new fragment shader source was compiled successfully.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
@ -63,9 +66,3 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

Просмотреть файл

@ -11,8 +11,13 @@ function ifWebGLSupported() {
let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
reload(target);
let firstProgramActor = yield once(gFront, "program-linked");
let secondProgramActor = yield once(gFront, "program-linked");
yield promise.all([
once(gFront, "program-linked"),
once(gFront, "program-linked")
]);
yield once(panel.panelWin, EVENTS.SOURCES_SHOWN)
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
@ -78,9 +83,3 @@ function ifWebGLSupported() {
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}

Просмотреть файл

@ -9,8 +9,7 @@ function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
front.setup({ reload: true });
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
isnot(firstProgramActor, secondProgramActor,
"Two distinct program actors were recevide from two separate contexts.");

Просмотреть файл

@ -10,8 +10,8 @@ function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
front.setup({ reload: true });
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
let firstFragmentShader = yield firstProgramActor.getFragmentShader();
let secondFragmentShader = yield secondProgramActor.getFragmentShader();

Просмотреть файл

@ -16,8 +16,7 @@ function ifWebGLSupported() {
ok(true, "The cached programs behave correctly before the navigation.");
navigate(target, MULTIPLE_CONTEXTS_URL);
let secondProgram = yield once(front, "program-linked");
let thirdProgram = yield once(front, "program-linked");
let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
yield checkSecondCachedPrograms(firstProgram, [secondProgram, thirdProgram]);
yield checkHighlightingInTheSecondPage(secondProgram, thirdProgram);
ok(true, "The cached programs behave correctly after the navigation.");

Просмотреть файл

@ -23,8 +23,7 @@ function ifWebGLSupported() {
// 1. Perform a simple navigation.
navigate(target, MULTIPLE_CONTEXTS_URL);
let secondProgram = yield once(front, "program-linked");
let thirdProgram = yield once(front, "program-linked");
let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
let programs = yield front.getPrograms();
is(programs.length, 2,
"The second and third programs should be returned by a call to getPrograms().");
@ -65,8 +64,7 @@ function ifWebGLSupported() {
is(programs.length, 0,
"There should be no cached program actors yet.");
yield once(front, "program-linked");
yield once(front, "program-linked");
yield getPrograms(front, 2);
yield globalCreated;
let programs = yield front.getPrograms();
is(programs.length, 2,

Просмотреть файл

@ -10,8 +10,7 @@ function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(OVERLAPPING_GEOMETRY_CANVAS_URL);
front.setup({ reload: true });
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);

Просмотреть файл

@ -48,7 +48,7 @@
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(0);

Просмотреть файл

@ -42,7 +42,7 @@
window.onload = function() {
for (let i = 0; i < 2; i++) {
canvas[i] = document.querySelector("#canvas" + (i + 1));
gl[i] = canvas[i].getContext("webgl");
gl[i] = canvas[i].getContext("webgl", { preserveDrawingBuffer: true });
gl[i].clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(i);

Просмотреть файл

@ -48,7 +48,7 @@
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(0);

Просмотреть файл

@ -35,7 +35,7 @@
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
let shaderProgram = gl.createProgram();
let vertexShader, fragmentShader;

Просмотреть файл

@ -44,7 +44,7 @@
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram();

Просмотреть файл

@ -12,7 +12,7 @@ let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", true);
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
@ -124,14 +124,14 @@ function once(aTarget, aEventName, aUseCapture = false) {
let deferred = promise.defer();
for (let [add, remove] of [
["on", "off"], // Use event emitter before DOM events for consistency
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
["on", "off"]
["addListener", "removeListener"]
]) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
aTarget[remove](aEventName, onEvent, aUseCapture);
deferred.resolve.apply(deferred, aArgs);
deferred.resolve(...aArgs);
}, aUseCapture);
break;
}
@ -140,6 +140,16 @@ function once(aTarget, aEventName, aUseCapture = false) {
return deferred.promise;
}
// Hack around `once`, as that only resolves to a single (first) argument
// and discards the rest. `onceSpread` is similar, except resolves to an
// array of all of the arguments in the handler. These should be consolidated
// into the same function, but many tests will need to be changed.
function onceSpread(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (...args) => deferred.resolve(args));
return deferred.promise;
}
function observe(aNotificationName, aOwnsWeak = false) {
info("Waiting for observer notification: '" + aNotificationName + ".");
@ -273,3 +283,27 @@ function teardown(aPanel) {
removeTab(aPanel.target.tab)
]);
}
// Due to `program-linked` events firing synchronously, we cannot
// just yield/chain them together, as then we miss all actors after the
// first event since they're fired consecutively. This allows us to capture
// all actors and returns an array containing them.
//
// Takes a `front` object that is an event emitter, the number of
// programs that should be listened to and waited on, and an optional
// `onAdd` function that calls with the entire actors array on program link
function getPrograms(front, count, onAdd) {
let actors = [];
let deferred = promise.defer();
front.on("program-linked", function onLink (actor) {
if (actors.length !== count) {
actors.push(actor);
if (typeof onAdd === 'function') onAdd(actors)
}
if (actors.length === count) {
front.off("program-linked", onLink);
deferred.resolve(actors);
}
});
return deferred.promise;
}

Просмотреть файл

@ -77,6 +77,7 @@ this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
this._parent = aParentNode;
this._parent.classList.add("variables-view-container");
this._parent.classList.add("theme-body");
this._appendEmptyNotice();
this._onSearchboxInput = this._onSearchboxInput.bind(this);

Просмотреть файл

@ -654,7 +654,7 @@ this.WidgetMethods = {
* - relaxed: true if this container should allow dupes & degenerates
* - attachment: some attached primitive/object for the item
* - attributes: a batch of attributes set to the displayed element
* - finalize: function invokde when the item is removed
* - finalize: function invoked when the item is removed
* @return Item
* The item associated with the displayed element if an unstaged push,
* undefined if the item was staged for a later commit.
@ -733,6 +733,7 @@ this.WidgetMethods = {
}
this._widget.removeChild(aItem._target);
this._untangleItem(aItem);
if (!this.itemCount) this.empty();
},
/**
@ -1009,7 +1010,9 @@ this.WidgetMethods = {
// a redundant selection event, so return early.
if (targetElement != prevElement) {
this._widget.selectedItem = targetElement;
ViewHelpers.dispatchEvent(targetElement || prevElement, "select", aItem);
let dispTarget = targetElement || prevElement;
let dispName = this.suppressSelectionEvents ? "suppressed-select" : "select";
ViewHelpers.dispatchEvent(dispTarget, dispName, aItem);
}
// Updates this container to reflect the information provided by the
@ -1044,6 +1047,15 @@ this.WidgetMethods = {
set selectedValue(aValue)
this.selectedItem = this._itemsByValue.get(aValue),
/**
* Specifies if "select" events dispatched from the elements in this container
* when their respective items are selected should be suppressed or not.
*
* If this flag is set to true, then consumers of this container won't
* be normally notified when items are selected.
*/
suppressSelectionEvents: false,
/**
* Focus this container the first time an element is inserted?
*
@ -1305,6 +1317,14 @@ this.WidgetMethods = {
return null;
},
/**
* Shortcut function for getItemForPredicate which works on item attachments.
* @see getItemForPredicate
*/
getItemForAttachment: function(aPredicate, aOwner = this) {
return this.getItemForPredicate(e => aPredicate(e.attachment));
},
/**
* Finds the index of an item in the container.
*

Просмотреть файл

@ -236,7 +236,13 @@ Editor.prototype = {
cm.on("focus", () => this.emit("focus"));
cm.on("scroll", () => this.emit("scroll"));
cm.on("change", () => this.emit("change"));
cm.on("change", () => {
this.emit("change");
if (!this._lastDirty) {
this._lastDirty = true;
this.emit("dirty-change");
}
});
cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
cm.on("gutterClick", (cm, line, gutter, ev) => {
@ -614,6 +620,8 @@ Editor.prototype = {
setClean: function () {
let cm = editors.get(this);
this.version = cm.changeGeneration();
this._lastDirty = false;
this.emit("dirty-change");
return this.version;
},

Просмотреть файл

@ -234,7 +234,7 @@ StyleSheetEditor.prototype = {
this.emit("source-editor-load");
});
sourceEditor.on("change", this._onPropertyChange);
sourceEditor.on("dirty-change", this._onPropertyChange);
},
/**

Просмотреть файл

@ -14,7 +14,6 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
let FileUtils = tempScope.FileUtils;
let NetUtil = tempScope.NetUtil;
function test()
{
waitForExplicitFinish();
@ -41,9 +40,22 @@ function test()
function runTests(editor)
{
editor.sourceEditor.once("dirty-change", () => {
is(editor.sourceEditor.isClean(), false, "Editor is dirty.");
ok(editor.summary.classList.contains("unsaved"),
"Star icon is present in the corresponding summary.");
});
let beginCursor = {line: 0, ch: 0};
editor.sourceEditor.replaceText("DIRTY TEXT", beginCursor, beginCursor);
editor.sourceEditor.once("dirty-change", () => {
is(editor.sourceEditor.isClean(), true, "Editor is clean.");
ok(!editor.summary.classList.contains("unsaved"),
"Star icon is not present in the corresponding summary.");
finish();
});
editor.saveToFile(null, function (file) {
ok(file, "file should get saved directly when using a file:// URI");
finish();
});
}

Просмотреть файл

@ -3322,10 +3322,12 @@ JSTerm.prototype = {
iframe.addEventListener("load", function onIframeLoad(aEvent) {
iframe.removeEventListener("load", onIframeLoad, true);
iframe.style.visibility = "visible";
deferred.resolve(iframe.contentWindow);
}, true);
iframe.flex = 1;
iframe.style.visibility = "hidden";
iframe.setAttribute("src", VARIABLES_VIEW_URL);
aOptions.targetElement.appendChild(iframe);
}

Просмотреть файл

@ -129,8 +129,10 @@
<!ENTITY debuggerUI.seMenuCondBreak "Add conditional breakpoint">
<!ENTITY debuggerUI.seMenuCondBreak.key "B">
<!-- LOCALIZATION NOTE (debuggerUI.instruments.*): This is the text that
- appears in the debugger's instruments pane tabs. -->
<!-- LOCALIZATION NOTE (debuggerUI.tabs.*): This is the text that
- appears in the debugger's side pane tabs. -->
<!ENTITY debuggerUI.tabs.sources "Sources">
<!ENTITY debuggerUI.tabs.callstack "Call Stack">
<!ENTITY debuggerUI.tabs.variables "Variables">
<!ENTITY debuggerUI.tabs.events "Events">

Просмотреть файл

@ -69,10 +69,14 @@ noGlobalsText=No globals
# when there are no scripts.
noSourcesText=This page has no sources.
# LOCALIZATION NOTE (noEventsTExt): The text to display in the events tab
# LOCALIZATION NOTE (noEventListenersText): The text to display in the events tab
# when there are no events.
noEventListenersText=No event listeners to display
# LOCALIZATION NOTE (noStackFramesText): The text to display in the call stack tab
# when there are no stack frames.
noStackFramesText=No stack frames to display
# LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
# the user hovers over the checkbox used to toggle an event breakpoint.
eventCheckboxTooltip=Toggle breaking on this event

Просмотреть файл

@ -247,7 +247,7 @@ Desktop browser's sync prefs.
completeselectedindex="true"
placeholder="&urlbar.emptytext;"
tabscrolling="true"
onclick="SelectionHelperUI.urlbarClick();"/>
onclick="SelectionHelperUI.urlbarTextboxClick(this);"/>
<toolbarbutton id="go-button" class="urlbar-button"
command="cmd_go"/>

Просмотреть файл

@ -34,10 +34,9 @@ var ChromeSelectionHandler = {
this._domWinUtils = Util.getWindowUtils(window);
this._contentWindow = window;
this._targetElement = this._domWinUtils.elementFromPoint(aJson.xPos, aJson.yPos, true, false);
this._targetIsEditable = this._targetElement instanceof Components.interfaces.nsIDOMXULTextBoxElement;
if (!this._targetIsEditable) {
this._onFail("not an editable?");
this._onFail("not an editable?", this._targetElement);
return;
}

Просмотреть файл

@ -344,9 +344,9 @@ var SelectionHelperUI = {
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "attach_edit_session_to_content":
let event = aSubject;
this.attachEditSession(Browser.selectedTab.browser,
event.clientX, event.clientY);
// We receive this from text input bindings when this module
// isn't accessible.
this.chromeTextboxClick(aSubject);
break;
case "apzc-transform-begin":
@ -514,6 +514,40 @@ var SelectionHelperUI = {
});
},
/*
* Event handler on the navbar text input. Called from navbar bindings
* when focus is applied to the edit.
*/
urlbarTextboxClick: function(aEdit) {
// workaround for bug 925457: taping browser chrome resets last tap
// co-ordinates to 'undefined' so that we know not to shift the browser
// when the keyboard is up in SelectionHandler's _calcNewContentPosition().
Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:ResetLastPos", {
xPos: null,
yPos: null
});
if (InputSourceHelper.isPrecise || !aEdit.textLength) {
return;
}
// Enable selection when there's text in the control
let innerRect = aEdit.inputField.getBoundingClientRect();
this.attachEditSession(ChromeSelectionHandler,
innerRect.left,
innerRect.top);
},
/*
* Click handler for chrome pages loaded into the browser (about:config).
* Called from the text input bindings via the attach_edit_session_to_content
* observer.
*/
chromeTextboxClick: function (aEvent) {
this.attachEditSession(Browser.selectedTab.browser,
aEvent.clientX, aEvent.clientY);
},
/*
* Handy debug routines that work independent of selection. They
* make use of the selection overlay for drawing points.
@ -835,20 +869,10 @@ var SelectionHelperUI = {
* Event handlers for document events
*/
urlbarClick: function() {
// Workaround for bug 925457: taping browser chrome resets last tap
// co-ordinates to 'undefined' so that we know not to shift the browser
// when the keyboard is up (in SelectionHandler._calcNewContentPosition())
Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:ResetLastPos", {
xPos: null,
yPos: null
});
},
/*
* Handles taps that move the current caret around in text edits,
* clear active selection and focus when neccessary, or change
* modes.
* modes. Only active afer SelectionHandlerUI is initialized.
*/
_onClick: function(aEvent) {
if (this.layerMode == kChromeLayer && this._targetIsEditable) {

Просмотреть файл

@ -44,7 +44,6 @@ pref("layers.componentalpha.enabled", false);
pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
pref("apz.pan_repaint_interval", 50); // prefer 20 fps
pref("apz.fling_repaint_interval", 50); // prefer 20 fps
pref("apz.fling_friction", "0.002");
pref("apz.fling_stopped_threshold", "0.2");
// 0 = free, 1 = standard, 2 = sticky

Просмотреть файл

@ -9,6 +9,10 @@
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
}
@ -20,7 +24,7 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar .devtools-toolbarbutton {
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
@ -87,15 +91,23 @@
padding: 2px;
}
.list-widget-item:not(.selected):not(.empty):hover {
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
}
.list-widget-item.selected.light {
.theme-light .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
color: #000;
}
.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
}
.theme-dark .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
}
.list-widget-item.selected {
background: Highlight;
color: HighlightText;
@ -106,21 +118,52 @@
padding: 2px;
}
/* Stack frames view */
/* Breadcrumbs stack frames view */
.breadcrumbs-widget-item {
max-width: none;
}
.dbg-stackframe-details {
-moz-padding-start: 4px;
}
.dbg-stackframe-menuitem[checked] {
margin-top: 3px;
margin-bottom: 3px;
outline: 1px solid #eee;
font-weight: 600;
/* Classic stack frames view */
.dbg-classic-stackframe {
display: block;
padding: 4px;
}
.dbg-stackframe-menuitem-details {
-moz-padding-start: 16px;
.dbg-classic-stackframe-title {
font-weight: 600;
color: #046;
}
.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
float: right;
}
.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
float: left;
}
.dbg-classic-stackframe-details-url {
max-width: 90%;
text-align: end;
color: #666;
}
.dbg-classic-stackframe-details-sep {
color: #aaa;
}
.dbg-classic-stackframe-details-line {
color: #58b;
}
#callstack-list .side-menu-widget-item.selected label {
color: #fff;
}
/* Sources and breakpoints view */
@ -180,8 +223,9 @@
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}
/* Watch expressions view */
@ -203,6 +247,7 @@
.dbg-expression-input {
-moz-padding-start: 2px !important;
color: inherit;
}
/* Event listeners view */
@ -227,6 +272,10 @@
color: #666;
}
#event-listeners .side-menu-widget-item.selected {
background: none !important;
}
/* Searchbox and the search operations help panel */
#searchbox {
@ -380,6 +429,11 @@
/* Toolbar controls */
.devtools-sidebar-tabs > tabs > tab {
min-height: 25px !important;
padding: 0 !important;
}
#resumption-panel-desc {
width: 200px;
}
@ -468,12 +522,17 @@
max-height: 80vh;
}
#body[layout=vertical] #sources-pane > tabs {
-moz-border-end: none;
}
#body[layout=vertical] #instruments-pane {
margin: 0 !important;
/* To prevent all the margin hacks to hide the sidebar. */
}
#body[layout=vertical] .side-menu-widget-container {
#body[layout=vertical] .side-menu-widget-container,
#body[layout=vertical] .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}

Просмотреть файл

@ -312,6 +312,10 @@ box.requests-menu-status[code^="5"] {
/* SideMenuWidget */
.side-menu-widget-container {
box-shadow: none !important;
}
.side-menu-widget-item[odd] {
background: rgba(255,255,255,0.05);
}
@ -363,10 +367,6 @@ box.requests-menu-status[code^="5"] {
-moz-padding-start: 3px;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #10c !important;
}
/* Headers tabpanel */
#headers-summary-status,

Просмотреть файл

@ -281,15 +281,17 @@
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container:-moz-locale-dir(ltr),
.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container:-moz-locale-dir(rtl),
.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
.side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
@ -328,7 +330,9 @@
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
@ -339,15 +343,11 @@
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
.side-menu-widget-item.selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -432,10 +432,6 @@
/* VariablesView */
.variables-view-container {
background: #fff;
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -445,11 +441,6 @@
color: #fff;
}
.variables-view-scope:focus > .title {
background: Highlight;
color: HighlightText;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
@ -458,13 +449,12 @@
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out;
color: #000;
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
background: rgba(255,255,0,0.65);
transition-duration: 0.4s;
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
@ -473,12 +463,6 @@
-moz-padding-end: 4px;
}
.variable-or-property:focus > .title {
background: Highlight;
color: HighlightText;
border-radius: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
@ -495,52 +479,22 @@
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
.variables-view-variable > .title > .name {
font-weight: 600;
}
.variables-view-variable:not(:focus) > .title > .name {
color: #048;
}
.variables-view-property:not(:focus) > .title > .name {
color: #881090;
}
/* Token value colors */
.variable-or-property:not(:focus) > .title > .token-undefined {
color: #bbb;
}
.variable-or-property:not(:focus) > .title > .token-null {
color: #999;
}
.variable-or-property:not(:focus) > .title > .token-boolean {
color: #10c;
}
.variable-or-property:not(:focus) > .title > .token-number {
color: #c00;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #282;
}
.variable-or-property:not(:focus) > .title > .token-other {
color: #333;
.variable-or-property:focus > .title > label {
color: inherit !important;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
opacity: 0.5;
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
@ -551,6 +505,10 @@
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property-non-writable-icon {
background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
width: 16px;
@ -577,27 +535,6 @@
color: #666;
}
/* Special variables and properties */
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property[exception]:not(:focus) > .title > .name {
color: #a00;
text-shadow: 0 0 8px #fcc;
}
.variable-or-property[return]:not(:focus) > .title > .name {
color: #0a0;
text-shadow: 0 0 8px #cfc;
}
.variable-or-property[scope]:not(:focus) > .title > .name {
color: #00a;
text-shadow: 0 0 8px #ccf;
}
/* Aligned values */
.variables-view-container[aligned-values] .title > .separator {
@ -634,7 +571,7 @@
.variable-or-property[non-configurable] > tooltip > label[value=configurable],
.variable-or-property[non-writable] > tooltip > label[value=writable],
.variable-or-property[non-extensible] > tooltip > label[value=extensible] {
color: #f44;
color: #800;
text-decoration: line-through;
}
@ -680,14 +617,13 @@
.element-name-input {
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
color: #048;
font-weight: 600;
}
.element-value-input,
.element-name-input {
border: 1px solid #999 !important;
box-shadow: 1px 2px 4px #aaa;
border: 1px solid rgba(128, 128, 128, .5) !important;
color: inherit;
}
/* Variables and properties searching */

Просмотреть файл

@ -3594,11 +3594,16 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
#navigator-toolbox[inFullscreen]:not(:-moz-lwtheme)::before {
height: calc(@tabHeight@ + 11px) !important;
}
#main-window[inFullscreen][privatebrowsingmode=temporary],
#main-window[inFullscreen]:-moz-lwtheme {
/* This additional padding matches the change in height in the pseudo-element
* above. The rules combined force the top 22px of the background image to
* be hidden, so there image doesn't jump around with the loss of the titlebar */
* above. */
padding-top: 11px;
}
#main-window[inFullscreen]:not([privatebrowsingmode=temporary]):-moz-lwtheme {
/* In combination with the previous rule, forces the top 22px of the
* background image to be hidden, so the image doesn't jump around with
* the loss of the titlebar. */
background-position: right -11px;
}
}

Просмотреть файл

@ -11,6 +11,10 @@
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
}
@ -22,7 +26,7 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar .devtools-toolbarbutton {
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
@ -89,15 +93,23 @@
padding: 2px;
}
.list-widget-item:not(.selected):not(.empty):hover {
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
}
.list-widget-item.selected.light {
.theme-light .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
color: #000;
}
.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
}
.theme-dark .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
}
.list-widget-item.selected {
background: Highlight;
color: HighlightText;
@ -108,21 +120,52 @@
padding: 2px;
}
/* Stack frames view */
/* Breadcrumbs stack frames view */
.breadcrumbs-widget-item {
max-width: none;
}
.dbg-stackframe-details {
-moz-padding-start: 4px;
}
.dbg-stackframe-menuitem[checked] {
margin-top: 3px;
margin-bottom: 3px;
outline: 1px solid #eee;
font-weight: 600;
/* Classic stack frames view */
.dbg-classic-stackframe {
display: block;
padding: 4px;
}
.dbg-stackframe-menuitem-details {
-moz-padding-start: 16px;
.dbg-classic-stackframe-title {
font-weight: 600;
color: #046;
}
.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
float: right;
}
.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
float: left;
}
.dbg-classic-stackframe-details-url {
max-width: 90%;
text-align: end;
color: #666;
}
.dbg-classic-stackframe-details-sep {
color: #aaa;
}
.dbg-classic-stackframe-details-line {
color: #58b;
}
#callstack-list .side-menu-widget-item.selected label {
color: #fff;
}
/* Sources and breakpoints view */
@ -182,8 +225,9 @@
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}
/* Watch expressions view */
@ -205,6 +249,7 @@
.dbg-expression-input {
-moz-padding-start: 2px !important;
color: inherit;
}
/* Event listeners view */
@ -229,6 +274,10 @@
color: #666;
}
#event-listeners .side-menu-widget-item.selected {
background: none !important;
}
/* Searchbox and the search operations help panel */
#searchbox {
@ -382,6 +431,11 @@
/* Toolbar controls */
.devtools-sidebar-tabs > tabs > tab {
min-height: 1em !important;
padding: 0 !important;
}
#resumption-panel-desc {
width: 200px;
}
@ -470,12 +524,17 @@
max-height: 80vh;
}
#body[layout=vertical] #sources-pane > tabs {
-moz-border-end: none;
}
#body[layout=vertical] #instruments-pane {
margin: 0 !important;
/* To prevent all the margin hacks to hide the sidebar. */
}
#body[layout=vertical] .side-menu-widget-container {
#body[layout=vertical] .side-menu-widget-container,
#body[layout=vertical] .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}

Просмотреть файл

@ -312,6 +312,10 @@ box.requests-menu-status[code^="5"] {
/* SideMenuWidget */
.side-menu-widget-container {
box-shadow: none !important;
}
.side-menu-widget-item[odd] {
background: rgba(255,255,255,0.05);
}
@ -363,10 +367,6 @@ box.requests-menu-status[code^="5"] {
-moz-padding-start: 3px;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #10c !important;
}
/* Headers tabpanel */
#headers-summary-status,

Просмотреть файл

@ -281,15 +281,17 @@
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container:-moz-locale-dir(ltr),
.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container:-moz-locale-dir(rtl),
.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
.side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
@ -328,7 +330,9 @@
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
@ -339,15 +343,11 @@
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
.side-menu-widget-item.selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -426,10 +426,6 @@
/* VariablesView */
.variables-view-container {
background: #fff;
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -439,11 +435,6 @@
color: #fff;
}
.variables-view-scope:focus > .title {
background: Highlight;
color: HighlightText;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
@ -452,13 +443,12 @@
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out;
color: #000;
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
background: rgba(255,255,0,0.65);
transition-duration: 0.4s;
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
@ -467,12 +457,6 @@
-moz-padding-end: 4px;
}
.variable-or-property:focus > .title {
background: Highlight;
color: HighlightText;
border-radius: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
@ -489,52 +473,22 @@
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
.variables-view-variable > .title > .name {
font-weight: 600;
}
.variables-view-variable:not(:focus) > .title > .name {
color: #048;
}
.variables-view-property:not(:focus) > .title > .name {
color: #881090;
}
/* Token value colors */
.variable-or-property:not(:focus) > .title > .token-undefined {
color: #bbb;
}
.variable-or-property:not(:focus) > .title > .token-null {
color: #999;
}
.variable-or-property:not(:focus) > .title > .token-boolean {
color: #10c;
}
.variable-or-property:not(:focus) > .title > .token-number {
color: #c00;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #282;
}
.variable-or-property:not(:focus) > .title > .token-other {
color: #333;
.variable-or-property:focus > .title > label {
color: inherit !important;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
opacity: 0.5;
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
@ -545,6 +499,10 @@
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property-non-writable-icon {
background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
width: 16px;
@ -571,27 +529,6 @@
color: #666;
}
/* Special variables and properties */
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property[exception]:not(:focus) > .title > .name {
color: #a00;
text-shadow: 0 0 8px #fcc;
}
.variable-or-property[return]:not(:focus) > .title > .name {
color: #0a0;
text-shadow: 0 0 8px #cfc;
}
.variable-or-property[scope]:not(:focus) > .title > .name {
color: #00a;
text-shadow: 0 0 8px #ccf;
}
/* Aligned values */
.variables-view-container[aligned-values] .title > .separator {
@ -674,14 +611,13 @@
.element-name-input {
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
color: #048;
font-weight: 600;
}
.element-value-input,
.element-name-input {
border: 1px solid #999 !important;
box-shadow: 1px 2px 4px #aaa;
border: 1px solid rgba(128, 128, 128, .5) !important;
color: inherit;
}
/* Variables and properties searching */

Просмотреть файл

@ -51,7 +51,8 @@
background-color: rgba(0,0,0,0.5);
}
.theme-bg-contrast { /* contrast bg color to attract attention on a container */
.theme-bg-contrast,
.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
background: #a18650;
}
@ -73,7 +74,9 @@
.theme-comment,
.cm-s-mozilla .cm-meta,
.cm-s-mozilla .cm-hr,
.cm-s-mozilla .cm-comment { /* grey */
.cm-s-mozilla .cm-comment,
.variable-or-property .token-undefined,
.variable-or-property .token-null { /* grey */
color: #5c6773;
}
@ -88,7 +91,9 @@
}
.theme-fg-color1,
.cm-s-mozilla .cm-number { /* green */
.cm-s-mozilla .cm-number,
.variable-or-property .token-number,
.variable-or-property[return] > .title > .name { /* green */
color: #5c9966;
}
@ -97,14 +102,18 @@
.cm-s-mozilla .cm-variable,
.cm-s-mozilla .cm-def,
.cm-s-mozilla .cm-property,
.cm-s-mozilla .cm-qualifier { /* blue */
.cm-s-mozilla .cm-qualifier,
.variables-view-variable > .title > .name,
.variable-or-property[scope] > .title > .name { /* blue */
color: #3689b2;
}
.theme-fg-color3,
.cm-s-mozilla .cm-builtin,
.cm-s-mozilla .cm-tag,
.cm-s-mozilla .cm-header { /* pink/lavender */
.cm-s-mozilla .cm-header,
.variables-view-property > .title > .name,
.variable-or-property[safe-getter] > .title > .name { /* pink/lavender */
color: #a673bf;
}
@ -120,14 +129,17 @@
.theme-fg-color6,
.cm-s-mozilla .cm-string,
.cm-s-mozilla .cm-string-2 { /* Orange */
.cm-s-mozilla .cm-string-2,
.variable-or-property .token-string { /* Orange */
color: #b26b47;
}
.theme-fg-color7,
.cm-s-mozilla .cm-atom,
.cm-s-mozilla .cm-quote,
.cm-s-mozilla .cm-error { /* Red */
.cm-s-mozilla .cm-error,
.variable-or-property .token-boolean,
.variable-or-property[exception] > .title > .name { /* Red */
color: #bf5656;
}
@ -148,6 +160,12 @@
box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
}
.variables-view-scope:focus > .title,
.variable-or-property:focus > .title {
background: #3689b2; /* fg-color2 */
color: white;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */

Просмотреть файл

@ -51,7 +51,8 @@
background: #EFEFEF;
}
.theme-bg-contrast { /* contrast bg color to attract attention on a container */
.theme-bg-contrast,
.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
background: #a18650;
}
@ -72,7 +73,9 @@
.theme-comment,
.cm-s-mozilla .cm-meta,
.cm-s-mozilla .cm-hr,
.cm-s-mozilla .cm-comment { /* grey */
.cm-s-mozilla .cm-comment,
.variable-or-property .token-undefined,
.variable-or-property .token-null { /* grey */
color: hsl(90,2%,46%);
}
@ -87,7 +90,9 @@
}
.theme-fg-color1,
.cm-s-mozilla .cm-number { /* green */
.cm-s-mozilla .cm-number,
.variable-or-property .token-number,
.variable-or-property[return] > .title > .name { /* green */
color: hsl(72,100%,27%);
}
@ -96,14 +101,18 @@
.cm-s-mozilla .cm-builtin,
.cm-s-mozilla .cm-def,
.cm-s-mozilla .cm-property,
.cm-s-mozilla .cm-qualifier { /* blue */
.cm-s-mozilla .cm-qualifier,
.variables-view-variable > .title > .name,
.variable-or-property[scope] > .title > .name { /* blue */
color: hsl(208,56%,40%);
}
.theme-fg-color3,
.cm-s-mozilla .cm-variable,
.cm-s-mozilla .cm-tag,
.cm-s-mozilla .cm-header { /* dark blue */
.cm-s-mozilla .cm-header,
.variables-view-property > .title > .name,
.variable-or-property[safe-getter] > .title > .name { /* dark blue */
color: hsl(208,81%,21%)
}
@ -119,14 +128,17 @@
.theme-fg-color6,
.cm-s-mozilla .cm-string,
.cm-s-mozilla .cm-string-2 { /* Orange */
.cm-s-mozilla .cm-string-2,
.variable-or-property .token-string { /* Orange */
color: hsl(24,85%,39%);
}
.theme-fg-color7,
.cm-s-mozilla .cm-atom,
.cm-s-mozilla .cm-quote,
.cm-s-mozilla .cm-error { /* Red */
.cm-s-mozilla .cm-error,
.variable-or-property .token-boolean,
.variable-or-property[exception] > .title > .name { /* Red */
color: #bf5656;
}
@ -147,6 +159,12 @@
box-shadow: 0 0 0 1px #EFEFEF;
}
.variables-view-scope:focus > .title,
.variable-or-property:focus > .title {
background: hsl(208,56%,40%); /* fg-color2 */
color: white;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */

Просмотреть файл

@ -260,11 +260,15 @@
}
.devtools-sidebar-tabs > tabs > tab {
background-size: calc(100% - 2px) 100%, 1px 100%;
background-size: calc(100% - 1px) 100%, 1px 100%;
background-repeat: no-repeat;
background-position: 1px, 0;
}
.devtools-sidebar-tabs > tabs > tab:not(:last-of-type) {
background-size: calc(100% - 2px) 100%, 1px 100%;
}
.devtools-sidebar-tabs:-moz-locale-dir(rtl) > tabs > tab {
background-position: calc(100% - 1px), 100%;
}

Просмотреть файл

@ -291,11 +291,11 @@ a {
.inlined-variables-view iframe {
display: block;
flex: 1;
margin-top: 5px;
margin-bottom: 15px;
-moz-margin-end: 15px;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 0 12px #dfdfdf;
border: 1px solid rgba(128, 128, 128, .5);
border-radius: 3px;
}
#webconsole-sidebar > tabs {
@ -354,6 +354,10 @@ a {
background: #131c26; /* mainBackgroundColor */
}
.theme-dark .inlined-variables-view iframe {
border-color: #333;
}
.theme-light .jsterm-input-container {
background-color: #fff; /* mainBackgroundColor */
border-color: ThreeDShadow;
@ -370,3 +374,8 @@ a {
.theme-light .navigation-marker .url {
background: #fff; /* mainBackgroundColor */
}
.theme-light .inlined-variables-view iframe {
border-color: #ccc;
}

Просмотреть файл

@ -9,6 +9,10 @@
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
}
@ -20,7 +24,7 @@
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-toolbar .devtools-toolbarbutton {
#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
min-width: 32px;
}
@ -87,15 +91,23 @@
padding: 2px;
}
.list-widget-item:not(.selected):not(.empty):hover {
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
}
.list-widget-item.selected.light {
.theme-light .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
color: #000;
}
.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
}
.theme-dark .list-widget-item.selected.light {
background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
}
.list-widget-item.selected {
background: Highlight;
color: HighlightText;
@ -106,21 +118,52 @@
padding: 2px;
}
/* Stack frames view */
/* Breadcrumbs stack frames view */
.breadcrumbs-widget-item {
max-width: none;
}
.dbg-stackframe-details {
-moz-padding-start: 4px;
}
.dbg-stackframe-menuitem[checked] {
margin-top: 3px;
margin-bottom: 3px;
outline: 1px solid #eee;
font-weight: 600;
/* Classic stack frames view */
.dbg-classic-stackframe {
display: block;
padding: 4px;
}
.dbg-stackframe-menuitem-details {
-moz-padding-start: 16px;
.dbg-classic-stackframe-title {
font-weight: 600;
color: #046;
}
.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
float: right;
}
.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
float: left;
}
.dbg-classic-stackframe-details-url {
max-width: 90%;
text-align: end;
color: #666;
}
.dbg-classic-stackframe-details-sep {
color: #aaa;
}
.dbg-classic-stackframe-details-line {
color: #58b;
}
#callstack-list .side-menu-widget-item.selected label {
color: #fff;
}
/* Sources and breakpoints view */
@ -180,8 +223,9 @@
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
#instruments-pane .side-menu-widget-container,
#instruments-pane .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}
/* Watch expressions view */
@ -203,6 +247,7 @@
.dbg-expression-input {
-moz-padding-start: 2px !important;
color: inherit;
}
/* Event listeners view */
@ -227,6 +272,10 @@
color: #666;
}
#event-listeners .side-menu-widget-item.selected {
background: none !important;
}
/* Searchbox and the search operations help panel */
#searchbox {
@ -380,6 +429,11 @@
/* Toolbar controls */
.devtools-sidebar-tabs > tabs > tab {
min-height: 25px !important;
padding: 0 !important;
}
#resumption-panel-desc {
width: 200px;
}
@ -473,12 +527,17 @@
max-height: 80vh;
}
#body[layout=vertical] #sources-pane > tabs {
-moz-border-end: none;
}
#body[layout=vertical] #instruments-pane {
margin: 0 !important;
/* To prevent all the margin hacks to hide the sidebar. */
}
#body[layout=vertical] .side-menu-widget-container {
#body[layout=vertical] .side-menu-widget-container,
#body[layout=vertical] .side-menu-widget-empty-notice-container {
box-shadow: none !important;
}

Просмотреть файл

@ -312,6 +312,10 @@ box.requests-menu-status[code^="5"] {
/* SideMenuWidget */
.side-menu-widget-container {
box-shadow: none !important;
}
.side-menu-widget-item[odd] {
background: rgba(255,255,255,0.05);
}
@ -363,10 +367,6 @@ box.requests-menu-status[code^="5"] {
-moz-padding-start: 3px;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #10c !important;
}
/* Headers tabpanel */
#headers-summary-status,

Просмотреть файл

@ -285,15 +285,17 @@
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container:-moz-locale-dir(ltr),
.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container:-moz-locale-dir(rtl),
.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
.side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
@ -332,7 +334,9 @@
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
@ -343,15 +347,11 @@
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
.side-menu-widget-item.selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -429,10 +429,6 @@
/* VariablesView */
.variables-view-container {
background: #fff;
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -442,11 +438,6 @@
color: #fff;
}
.variables-view-scope:focus > .title {
background: Highlight;
color: HighlightText;
}
.variables-view-scope > .variables-view-element-details:not(:empty) {
-moz-margin-start: 2px;
-moz-margin-end: 1px;
@ -455,13 +446,12 @@
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out;
color: #000;
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
background: rgba(255,255,0,0.65);
transition-duration: 0.4s;
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
@ -470,12 +460,6 @@
-moz-padding-end: 4px;
}
.variable-or-property:focus > .title {
background: Highlight;
color: HighlightText;
border-radius: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
@ -492,52 +476,22 @@
}
.variables-view-variable:not(:last-child) {
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(128, 128, 128, .15);
}
.variables-view-variable > .title > .name {
font-weight: 600;
}
.variables-view-variable:not(:focus) > .title > .name {
color: #048;
}
.variables-view-property:not(:focus) > .title > .name {
color: #881090;
}
/* Token value colors */
.variable-or-property:not(:focus) > .title > .token-undefined {
color: #bbb;
}
.variable-or-property:not(:focus) > .title > .token-null {
color: #999;
}
.variable-or-property:not(:focus) > .title > .token-boolean {
color: #10c;
}
.variable-or-property:not(:focus) > .title > .token-number {
color: #c00;
}
.variable-or-property:not(:focus) > .title > .token-string {
color: #282;
}
.variable-or-property:not(:focus) > .title > .token-other {
color: #333;
.variable-or-property:focus > .title > label {
color: inherit !important;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
opacity: 0.5;
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
@ -548,6 +502,10 @@
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property-non-writable-icon {
background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
width: 16px;
@ -574,27 +532,6 @@
color: #666;
}
/* Special variables and properties */
.variable-or-property[safe-getter] > .title > .name {
border-bottom: 1px dashed #8b0;
}
.variable-or-property[exception]:not(:focus) > .title > .name {
color: #a00;
text-shadow: 0 0 8px #fcc;
}
.variable-or-property[return]:not(:focus) > .title > .name {
color: #0a0;
text-shadow: 0 0 8px #cfc;
}
.variable-or-property[scope]:not(:focus) > .title > .name {
color: #00a;
text-shadow: 0 0 8px #ccf;
}
/* Aligned values */
.variables-view-container[aligned-values] .title > .separator {
@ -677,14 +614,13 @@
.element-name-input {
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
color: #048;
font-weight: 600;
}
.element-value-input,
.element-name-input {
border: 1px solid #999 !important;
box-shadow: 1px 2px 4px #aaa;
border: 1px solid rgba(128, 128, 128, .5) !important;
color: inherit;
}
/* Variables and properties searching */

Просмотреть файл

@ -4,14 +4,12 @@
package org.mozilla.gecko;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.GeckoLayerClient.DrawListener;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.sqlite.SQLiteBridge;
import org.mozilla.gecko.util.GeckoEventListener;
import android.app.Activity;
import android.app.Instrumentation;
@ -24,112 +22,65 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import org.json.JSONObject;
import com.jayway.android.robotium.solo.Solo;
import static org.mozilla.gecko.FennecNativeDriver.LogLevel;
public class FennecNativeActions implements Actions {
private static final String LOGTAG = "FennecNativeActions";
private Solo mSolo;
private Instrumentation mInstr;
private Activity mGeckoApp;
private Assert mAsserter;
// Objects for reflexive access of fennec classes.
private ClassLoader mClassLoader;
private Class mApiClass;
private Class mEventListenerClass;
private Class mDrawListenerClass;
private Method mRegisterEventListener;
private Method mUnregisterEventListener;
private Method mBroadcastEvent;
private Method mPreferencesGetEvent;
private Method mPreferencesObserveEvent;
private Method mPreferencesRemoveObserversEvent;
private Method mSetDrawListener;
private Method mQuerySql;
private Object mRobocopApi;
private static final String LOGTAG = "FennecNativeActions";
public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) {
mSolo = robocop;
mInstr = instrumentation;
mGeckoApp = activity;
mAsserter = asserter;
// Set up reflexive access of java classes and methods.
try {
mClassLoader = activity.getClassLoader();
mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
mDrawListenerClass = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
mUnregisterEventListener = mApiClass.getMethod("unregisterEventListener", String.class, mEventListenerClass);
mBroadcastEvent = mApiClass.getMethod("broadcastEvent", String.class, String.class);
mPreferencesGetEvent = mApiClass.getMethod("preferencesGetEvent", Integer.TYPE, String[].class);
mPreferencesObserveEvent = mApiClass.getMethod("preferencesObserveEvent", Integer.TYPE, String[].class);
mPreferencesRemoveObserversEvent = mApiClass.getMethod("preferencesRemoveObserversEvent", Integer.TYPE);
mSetDrawListener = mApiClass.getMethod("setDrawListener", mDrawListenerClass);
mQuerySql = mApiClass.getMethod("querySql", String.class, String.class);
mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
} catch (Exception e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
}
class wakeInvocationHandler implements InvocationHandler {
private final GeckoEventExpecter mEventExpecter;
public wakeInvocationHandler(GeckoEventExpecter expecter) {
mEventExpecter = expecter;
}
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName = method.getName();
//Depending on the method, return a completely different type.
if(methodName.equals("toString")) {
return this.toString();
}
if(methodName.equals("equals")) {
return
args[0] == null ? false :
this.toString().equals(args[0].toString());
}
if(methodName.equals("clone")) {
return this;
}
if(methodName.equals("hashCode")) {
return 314;
}
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"Waking up on "+methodName);
mEventExpecter.notifyOfEvent(args);
return null;
}
GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath());
}
class GeckoEventExpecter implements RepeatedEventExpecter {
private final String mGeckoEvent;
private Object[] mRegistrationParams;
private boolean mEventEverReceived;
private String mEventData;
private BlockingQueue<String> mEventDataQueue;
private static final int MAX_WAIT_MS = 90000;
GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
private volatile boolean mIsRegistered;
private final String mGeckoEvent;
private final GeckoEventListener mListener;
private volatile boolean mEventEverReceived;
private String mEventData;
private BlockingQueue<String> mEventDataQueue;
GeckoEventExpecter(final String geckoEvent) {
if (TextUtils.isEmpty(geckoEvent)) {
throw new IllegalArgumentException("geckoEvent must not be empty");
}
if (registrationParams == null || registrationParams.length == 0) {
throw new IllegalArgumentException("registrationParams must not be empty");
}
mGeckoEvent = geckoEvent;
mRegistrationParams = registrationParams;
mEventDataQueue = new LinkedBlockingQueue<String>();
final GeckoEventExpecter expecter = this;
mListener = new GeckoEventListener() {
@Override
public void handleMessage(final String event, final JSONObject message) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"handleMessage called for: " + event + "; expecting: " + mGeckoEvent);
mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event");
expecter.notifyOfEvent(message);
}
};
GeckoAppShell.registerEventListener(mGeckoEvent, mListener);
mIsRegistered = true;
}
public void blockForEvent() {
@ -137,9 +88,10 @@ public class FennecNativeActions implements Actions {
}
private void blockForEvent(long millis, boolean failOnTimeout) {
if (mRegistrationParams == null) {
if (!mIsRegistered) {
throw new IllegalStateException("listener not registered");
}
try {
mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
@ -161,12 +113,13 @@ public class FennecNativeActions implements Actions {
}
public void blockUntilClear(long millis) {
if (mRegistrationParams == null) {
if (!mIsRegistered) {
throw new IllegalStateException("listener not registered");
}
if (millis <= 0) {
throw new IllegalArgumentException("millis must be > 0");
}
// wait for at least one event
try {
mEventData = mEventDataQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS);
@ -205,139 +158,82 @@ public class FennecNativeActions implements Actions {
}
public void unregisterListener() {
if (mRegistrationParams == null) {
if (!mIsRegistered) {
throw new IllegalStateException("listener not registered");
}
try {
FennecNativeDriver.log(LogLevel.INFO, "EventExpecter: no longer listening for "+mGeckoEvent);
mUnregisterEventListener.invoke(mRobocopApi, mRegistrationParams);
mRegistrationParams = null;
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
FennecNativeDriver.log(LogLevel.INFO,
"EventExpecter: no longer listening for " + mGeckoEvent);
GeckoAppShell.unregisterEventListener(mGeckoEvent, mListener);
mIsRegistered = false;
}
public synchronized boolean eventReceived() {
public boolean eventReceived() {
return mEventEverReceived;
}
void notifyOfEvent(Object[] args) {
void notifyOfEvent(final JSONObject message) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"received event " + mGeckoEvent);
synchronized (this) {
mEventEverReceived = true;
}
"received event " + mGeckoEvent);
mEventEverReceived = true;
try {
mEventDataQueue.put(args[1].toString());
mEventDataQueue.put(message.toString());
} catch (InterruptedException e) {
FennecNativeDriver.log(LogLevel.ERROR,
"EventExpecter dropped event: "+args[1].toString());
FennecNativeDriver.log(LogLevel.ERROR, e);
"EventExpecter dropped event: " + message.toString(), e);
}
}
}
public RepeatedEventExpecter expectGeckoEvent(String geckoEvent) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"waiting for "+geckoEvent);
try {
Object[] finalParams = new Object[2];
finalParams[0] = geckoEvent;
GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mEventListenerClass }, wIH);
finalParams[1] = proxy;
mRegisterEventListener.invoke(mRobocopApi, finalParams);
return expecter;
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
return null;
public RepeatedEventExpecter expectGeckoEvent(final String geckoEvent) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
return new GeckoEventExpecter(geckoEvent);
}
public void sendGeckoEvent(String geckoEvent, String data) {
try {
mBroadcastEvent.invoke(mRobocopApi, geckoEvent, data);
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
}
private void sendPreferencesEvent(Method method, int requestId, String[] prefNames) {
try {
method.invoke(mRobocopApi, requestId, prefNames);
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
public void sendGeckoEvent(final String geckoEvent, final String data) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(geckoEvent, data));
}
public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
sendPreferencesEvent(mPreferencesGetEvent, requestId, prefNames);
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
}
public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
sendPreferencesEvent(mPreferencesObserveEvent, requestId, prefNames);
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
}
public void sendPreferencesRemoveObserversEvent(int requestId) {
try {
mPreferencesRemoveObserversEvent.invoke(mRobocopApi, requestId);
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
}
class DrawListenerProxy implements InvocationHandler {
private final PaintExpecter mPaintExpecter;
DrawListenerProxy(PaintExpecter paintExpecter) {
mPaintExpecter = paintExpecter;
}
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName = method.getName();
if ("drawFinished".equals(methodName)) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"Received drawFinished notification");
mPaintExpecter.notifyOfEvent(args);
} else if ("toString".equals(methodName)) {
return "DrawListenerProxy";
} else if ("equals".equals(methodName)) {
return false;
} else if ("hashCode".equals(methodName)) {
return 0;
}
return null;
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
}
class PaintExpecter implements RepeatedEventExpecter {
private boolean mPaintDone;
private boolean mListening;
private static final int MAX_WAIT_MS = 90000;
PaintExpecter() throws IllegalAccessException, InvocationTargetException {
Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListenerClass }, new DrawListenerProxy(this));
mSetDrawListener.invoke(mRobocopApi, proxy);
private boolean mPaintDone;
private boolean mListening;
private final GeckoLayerClient mLayerClient;
PaintExpecter() {
final PaintExpecter expecter = this;
mLayerClient = GeckoAppShell.getLayerView().getLayerClient();
mLayerClient.setDrawListener(new DrawListener() {
@Override
public void drawFinished() {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"Received drawFinished notification");
expecter.notifyOfEvent();
}
});
mListening = true;
}
void notifyOfEvent(Object[] args) {
synchronized (this) {
mPaintDone = true;
this.notifyAll();
}
private synchronized void notifyOfEvent() {
mPaintDone = true;
this.notifyAll();
}
private synchronized void blockForEvent(long millis, boolean failOnTimeout) {
@ -431,23 +327,16 @@ public class FennecNativeActions implements Actions {
if (!mListening) {
throw new IllegalStateException("listener not registered");
}
try {
FennecNativeDriver.log(LogLevel.INFO, "PaintExpecter: no longer listening for events");
mListening = false;
mSetDrawListener.invoke(mRobocopApi, (Object)null);
} catch (Exception e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
FennecNativeDriver.log(LogLevel.INFO,
"PaintExpecter: no longer listening for events");
mLayerClient.setDrawListener(null);
mListening = false;
}
}
public RepeatedEventExpecter expectPaint() {
try {
return new PaintExpecter();
} catch (Exception e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
return null;
}
return new PaintExpecter();
}
public void sendSpecialKey(SpecialKey button) {
@ -495,14 +384,7 @@ public class FennecNativeActions implements Actions {
mSolo.drag(startingX, endingX, startingY, endingY, 10);
}
public Cursor querySql(String dbPath, String sql) {
try {
return (Cursor)mQuerySql.invoke(mRobocopApi, dbPath, sql);
} catch(InvocationTargetException ex) {
Log.e(LOGTAG, "Error invoking method", ex);
} catch(IllegalAccessException ex) {
Log.e(LOGTAG, "Error using field", ex);
}
return null;
public Cursor querySql(final String dbPath, final String sql) {
return new SQLiteBridge(dbPath).rawQuery(sql, null);
}
}

Просмотреть файл

@ -4,6 +4,10 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanningPerfAPI;
import org.mozilla.gecko.util.GeckoEventListener;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
@ -20,11 +24,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.view.View;
@ -46,18 +45,6 @@ public class FennecNativeDriver implements Driver {
private static String mLogFile = null;
private static LogLevel mLogLevel = LogLevel.INFO;
// Objects for reflexive access of fennec classes.
private ClassLoader mClassLoader;
private Class mApiClass;
private Class mEventListenerClass;
private Method mRegisterEventListener;
private Method mGetPixels;
private Method mStartFrameRecording;
private Method mStopFrameRecording;
private Method mStartCheckerboardRecording;
private Method mStopCheckerboardRecording;
private Object mRobocopApi;
public enum LogLevel {
DEBUG(1),
INFO(2),
@ -83,25 +70,6 @@ public class FennecNativeDriver implements Driver {
// Set up table of fennec_ids.
mLocators = convertTextToTable(getFile(mRootPath + "/fennec_ids.txt"));
// Set up reflexive access of java classes and methods.
try {
mClassLoader = activity.getClassLoader();
mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
mGetPixels = mApiClass.getMethod("getViewPixels", View.class);
mStartFrameRecording = mApiClass.getDeclaredMethod("startFrameTimeRecording");
mStopFrameRecording = mApiClass.getDeclaredMethod("stopFrameTimeRecording");
mStartCheckerboardRecording = mApiClass.getDeclaredMethod("startCheckerboardRecording");
mStopCheckerboardRecording = mApiClass.getDeclaredMethod("stopCheckerboardRecording");
mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
} catch (Exception e) {
log(LogLevel.ERROR, e);
}
}
//Information on the location of the Gecko Frame.
@ -172,99 +140,59 @@ public class FennecNativeDriver implements Driver {
}
public void startFrameRecording() {
try {
mStartFrameRecording.invoke(null);
} catch (IllegalAccessException e) {
log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
log(LogLevel.ERROR, e);
}
PanningPerfAPI.startFrameTimeRecording();
}
public int stopFrameRecording() {
try {
List<Long> frames = (List<Long>)mStopFrameRecording.invoke(null);
int badness = 0;
for (int i = 1; i < frames.size(); i++) {
long frameTime = frames.get(i) - frames.get(i - 1);
int delay = (int)(frameTime - FRAME_TIME_THRESHOLD);
// for each frame we miss, add the square of the delay. This
// makes large delays much worse than small delays.
if (delay > 0) {
badness += delay * delay;
}
final List<Long> frames = PanningPerfAPI.stopFrameTimeRecording();
int badness = 0;
for (int i = 1; i < frames.size(); i++) {
long frameTime = frames.get(i) - frames.get(i - 1);
int delay = (int)(frameTime - FRAME_TIME_THRESHOLD);
// for each frame we miss, add the square of the delay. This
// makes large delays much worse than small delays.
if (delay > 0) {
badness += delay * delay;
}
// Don't do any averaging of the numbers because really we want to
// know how bad the jank was at its worst
return badness;
} catch (IllegalAccessException e) {
log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
log(LogLevel.ERROR, e);
}
// higher values are worse, and the test failing is the worst!
return Integer.MAX_VALUE;
// Don't do any averaging of the numbers because really we want to
// know how bad the jank was at its worst
return badness;
}
public void startCheckerboardRecording() {
try {
mStartCheckerboardRecording.invoke(null);
} catch (IllegalAccessException e) {
log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
log(LogLevel.ERROR, e);
}
PanningPerfAPI.startCheckerboardRecording();
}
public float stopCheckerboardRecording() {
try {
List<Float> checkerboard = (List<Float>)mStopCheckerboardRecording.invoke(null);
float total = 0;
for (float val : checkerboard) {
total += val;
}
return total * 100.0f;
} catch (IllegalAccessException e) {
log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
log(LogLevel.ERROR, e);
final List<Float> checkerboard = PanningPerfAPI.stopCheckerboardRecording();
float total = 0;
for (float val : checkerboard) {
total += val;
}
return 0.0f;
return total * 100.0f;
}
private View getSurfaceView() {
ArrayList<View> views = mSolo.getCurrentViews();
try {
Class c = Class.forName("org.mozilla.gecko.gfx.LayerView");
for (View v : views) {
if (c.isInstance(v)) {
return v;
}
private LayerView getSurfaceView() {
final LayerView layerView = mSolo.getView(LayerView.class, 0);
if (layerView == null) {
log(LogLevel.WARN, "getSurfaceView could not find LayerView");
for (final View v : mSolo.getViews()) {
log(LogLevel.WARN, " View: " + v);
}
} catch (ClassNotFoundException e) {
log(LogLevel.ERROR, e);
}
log(LogLevel.WARN, "getSurfaceView could not find LayerView");
for (View v : views) {
log(LogLevel.WARN, v.toString());
}
return null;
return layerView;
}
public PaintedSurface getPaintedSurface() {
View view = getSurfaceView();
final LayerView view = getSurfaceView();
if (view == null) {
return null;
}
IntBuffer pixelBuffer;
try {
pixelBuffer = (IntBuffer)mGetPixels.invoke(mRobocopApi, view);
} catch (Exception e) {
log(LogLevel.ERROR, e);
return null;
}
final IntBuffer pixelBuffer = view.getPixels();
// now we need to (1) flip the image, because GL likes to do things up-side-down,
// and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
@ -312,27 +240,6 @@ public class FennecNativeDriver implements Driver {
public int mScrollHeight=0;
public int mPageHeight=10;
class scrollHandler implements InvocationHandler {
public scrollHandler(){};
public Object invoke(Object proxy, Method method, Object[] args) {
try {
// Disect the JSON object into the appropriate variables
JSONObject jo = ((JSONObject)args[1]);
mScrollHeight = jo.getInt("y");
mHeight = jo.getInt("cheight");
// We don't want a height of 0. That means it's a bad response.
if (mHeight > 0) {
mPageHeight = jo.getInt("height");
}
} catch( Throwable e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
"WARNING: ScrollReceived, but read wrong!");
}
return null;
}
}
public int getScrollHeight() {
return mScrollHeight;
}
@ -344,20 +251,23 @@ public class FennecNativeDriver implements Driver {
}
public void setupScrollHandling() {
//Setup scrollHandler to catch "robocop:scroll" events.
try {
Class [] interfaces = new Class[1];
interfaces[0] = mEventListenerClass;
Object[] finalParams = new Object[2];
finalParams[0] = "robocop:scroll";
finalParams[1] = Proxy.newProxyInstance(mClassLoader, interfaces, new scrollHandler());
mRegisterEventListener.invoke(mRobocopApi, finalParams);
} catch (IllegalAccessException e) {
log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
log(LogLevel.ERROR, e);
}
GeckoAppShell.registerEventListener("robocop:scroll", new GeckoEventListener() {
@Override
public void handleMessage(final String event, final JSONObject message) {
try {
mScrollHeight = message.getInt("y");
mHeight = message.getInt("cheight");
// We don't want a height of 0. That means it's a bad response.
if (mHeight > 0) {
mPageHeight = message.getInt("height");
}
} catch (JSONException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
"WARNING: ScrollReceived, but message does not contain " +
"expected fields: " + e);
}
}
});
}
/**

Просмотреть файл

@ -78,7 +78,7 @@ SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength)
aRetBuf[2] = aPacketLength & 0x00FF;
}
void
bool
ParseHeaders(const uint8_t* aHeaderStart,
int aTotalLength,
ObexHeaderSet* aRetHandlerSet)
@ -88,7 +88,7 @@ ParseHeaders(const uint8_t* aHeaderStart,
while (ptr - aHeaderStart < aTotalLength) {
ObexHeaderId headerId = (ObexHeaderId)*ptr++;
int contentLength = 0;
uint16_t contentLength = 0;
uint8_t highByte, lowByte;
// Defined in 2.1 OBEX Headers, IrOBEX 1.2
@ -101,7 +101,7 @@ ParseHeaders(const uint8_t* aHeaderStart,
// byte sequence, length prefixed with 2 byte unsigned integer.
highByte = *ptr++;
lowByte = *ptr++;
contentLength = (((int)highByte << 8) | lowByte) - 3;
contentLength = (((uint16_t)highByte << 8) | lowByte) - 3;
break;
case 0x02:
@ -115,10 +115,20 @@ ParseHeaders(const uint8_t* aHeaderStart,
break;
}
aRetHandlerSet->AddHeader(new ObexHeader(headerId, contentLength, ptr));
// Length check to prevent from memory pollusion.
if (ptr + contentLength > aHeaderStart + aTotalLength) {
// Severe error occurred. We can't even believe the received data, so
// clear all headers.
MOZ_ASSERT(false);
aRetHandlerSet->ClearHeaders();
return false;
}
aRetHandlerSet->AddHeader(new ObexHeader(headerId, contentLength, ptr));
ptr += contentLength;
}
return true;
}
END_BLUETOOTH_NAMESPACE

Просмотреть файл

@ -102,7 +102,8 @@ enum ObexResponseCode {
DatabaseLocked = 0xE1,
};
class ObexHeader {
class ObexHeader
{
public:
ObexHeader(ObexHeaderId aId, int aDataLength, const uint8_t* aData)
: mId(aId)
@ -122,11 +123,9 @@ public:
nsAutoArrayPtr<uint8_t> mData;
};
class ObexHeaderSet {
class ObexHeaderSet
{
public:
uint8_t mOpcode;
nsTArray<nsAutoPtr<ObexHeader> > mHeaders;
ObexHeaderSet(uint8_t aOpcode) : mOpcode(aOpcode)
{
}
@ -241,6 +240,15 @@ public:
return false;
}
void ClearHeaders()
{
mHeaders.Clear();
}
private:
uint8_t mOpcode;
nsTArray<nsAutoPtr<ObexHeader> > mHeaders;
};
int AppendHeaderName(uint8_t* aRetBuf, const char* aName, int aLength);
@ -249,7 +257,11 @@ int AppendHeaderEndOfBody(uint8_t* aRetBuf);
int AppendHeaderLength(uint8_t* aRetBuf, int aObjectLength);
int AppendHeaderConnectionId(uint8_t* aRetBuf, int aConnectionId);
void SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength);
void ParseHeaders(const uint8_t* aHeaderStart,
/**
* @return true when the message was parsed without any error, false otherwise.
*/
bool ParseHeaders(const uint8_t* aHeaderStart,
int aTotalLength,
ObexHeaderSet* aRetHanderSet);

Просмотреть файл

@ -803,25 +803,31 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
// Section 3.3.1 "Connect", IrOBEX 1.2
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
// [Headers:var]
ParseHeaders(&aMessage->mData[7],
receivedLength - 7,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToConnect();
AfterOppConnected();
} else if (opCode == ObexRequestCode::Abort) {
// Section 3.3.5 "Abort", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
ParseHeaders(&aMessage->mData[3],
receivedLength - 3,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToDisconnectOrAbort();
DeleteReceivedFile();
} else if (opCode == ObexRequestCode::Disconnect) {
// Section 3.3.2 "Disconnect", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
ParseHeaders(&aMessage->mData[3],
receivedLength - 3,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToDisconnectOrAbort();
AfterOppDisconnected();
FileTransferComplete();
@ -1197,7 +1203,7 @@ BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
void
BluetoothOppManager::ReplyError(uint8_t aError)
{
if (!mConnected) return;
BT_LOGR("error: %d", aError);
// Section 3.2 "Response Format", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]

Просмотреть файл

@ -341,9 +341,12 @@ Call::IsActive()
/**
* BluetoothHfpManager
*/
BluetoothHfpManager::BluetoothHfpManager() : mPhoneType(PhoneType::NONE)
, mController(nullptr)
BluetoothHfpManager::BluetoothHfpManager() : mController(nullptr)
{
#ifdef MOZ_B2G_RIL
mPhoneType = PhoneType::NONE;
#endif // MOZ_B2G_RIL
Reset();
}

Просмотреть файл

@ -819,25 +819,31 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
// Section 3.3.1 "Connect", IrOBEX 1.2
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
// [Headers:var]
ParseHeaders(&aMessage->mData[7],
receivedLength - 7,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToConnect();
AfterOppConnected();
} else if (opCode == ObexRequestCode::Abort) {
// Section 3.3.5 "Abort", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
ParseHeaders(&aMessage->mData[3],
receivedLength - 3,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToDisconnectOrAbort();
DeleteReceivedFile();
} else if (opCode == ObexRequestCode::Disconnect) {
// Section 3.3.2 "Disconnect", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
ParseHeaders(&aMessage->mData[3],
receivedLength - 3,
&pktHeaders);
if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToDisconnectOrAbort();
AfterOppDisconnected();
FileTransferComplete();
@ -1213,7 +1219,7 @@ BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
void
BluetoothOppManager::ReplyError(uint8_t aError)
{
if (!mConnected) return;
BT_LOGR("error: %d", aError);
// Section 3.2 "Response Format", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]

Просмотреть файл

@ -79,6 +79,7 @@ enum {
CAMERA_PARAM_PICTURESIZE,
CAMERA_PARAM_THUMBNAILSIZE,
CAMERA_PARAM_THUMBNAILQUALITY,
CAMERA_PARAM_SENSORANGLE,
CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
CAMERA_PARAM_SUPPORTED_VIDEOSIZES,

Просмотреть файл

@ -268,6 +268,14 @@ CameraControlImpl::Get(uint32_t aKey, idl::CameraSize& aSize)
return NS_OK;
}
nsresult
CameraControlImpl::Get(uint32_t aKey, int32_t* aValue)
{
MOZ_ASSERT(aValue);
*aValue = GetParameterInt32(aKey);
return NS_OK;
}
already_AddRefed<RecorderProfileManager>
CameraControlImpl::GetRecorderProfileManager()
{

Просмотреть файл

@ -75,6 +75,7 @@ public:
nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange);
nsresult Set(uint32_t aKey, const idl::CameraSize& aSize);
nsresult Get(uint32_t aKey, idl::CameraSize& aSize);
nsresult Get(uint32_t aKey, int32_t* aValue);
nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
{
@ -92,6 +93,7 @@ public:
virtual const char* GetParameter(const char* aKey) = 0;
virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
virtual double GetParameterDouble(uint32_t aKey) = 0;
virtual int32_t GetParameterInt32(uint32_t aKey) = 0;
virtual void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions) = 0;
virtual void GetParameter(uint32_t aKey, idl::CameraSize& aSize) = 0;
virtual void SetParameter(const char* aKey, const char* aValue) = 0;

Просмотреть файл

@ -285,6 +285,14 @@ nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
return compensation;
}
int32_t
nsDOMCameraControl::SensorAngle()
{
int32_t angle;
mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, &angle);
return angle;
}
already_AddRefed<nsICameraShutterCallback>
nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
{

Просмотреть файл

@ -76,6 +76,7 @@ public:
double GetFocusDistanceFar(ErrorResult& aRv);
void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
double GetExposureCompensation(ErrorResult& aRv);
int32_t SensorAngle();
already_AddRefed<nsICameraShutterCallback> GetOnShutter(ErrorResult& aRv);
void SetOnShutter(nsICameraShutterCallback* aCb, ErrorResult& aRv);
already_AddRefed<nsICameraClosedCallback> GetOnClosed(ErrorResult& aRv);

Просмотреть файл

@ -24,6 +24,7 @@ public:
const char* GetParameter(const char* aKey);
const char* GetParameterConstChar(uint32_t aKey);
double GetParameterDouble(uint32_t aKey);
int32_t GetParameterInt32(uint32_t aKey);
void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
void SetParameter(const char* aKey, const char* aValue);
@ -101,6 +102,12 @@ nsFallbackCameraControl::GetParameterDouble(uint32_t aKey)
return NAN;
}
int32_t
nsFallbackCameraControl::GetParameterInt32(uint32_t aKey)
{
return 0;
}
void
nsFallbackCameraControl::GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions)
{

Просмотреть файл

@ -434,6 +434,25 @@ nsGonkCameraControl::GetParameterDouble(uint32_t aKey)
}
}
int32_t
nsGonkCameraControl::GetParameterInt32(uint32_t aKey)
{
if (aKey == CAMERA_PARAM_SENSORANGLE) {
if (!mCameraHw.get()) {
return 0;
}
return mCameraHw->GetSensorOrientation();
}
const char* key = getKeyText(aKey);
if (!key) {
return 0;
}
RwAutoLockRead lock(mRwLock);
return mParams.getInt(key);
}
void
nsGonkCameraControl::GetParameter(uint32_t aKey,
nsTArray<idl::CameraRegion>& aRegions)

Просмотреть файл

@ -53,6 +53,7 @@ public:
const char* GetParameter(const char* aKey);
const char* GetParameterConstChar(uint32_t aKey);
double GetParameterDouble(uint32_t aKey);
int32_t GetParameterInt32(uint32_t aKey);
void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
void GetParameter(uint32_t aKey, nsTArray<idl::CameraSize>& aSizes);
void GetParameter(uint32_t aKey, idl::CameraSize& aSize);

Просмотреть файл

@ -46,6 +46,7 @@ public:
virtual nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange) = 0;
virtual nsresult Set(uint32_t aKey, const idl::CameraSize& aSize) = 0;
virtual nsresult Get(uint32_t aKey, idl::CameraSize& aSize) = 0;
virtual nsresult Get(uint32_t aKey, int32_t* aValue) = 0;
virtual nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue) = 0;
virtual nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue) = 0;
virtual nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes) = 0;

Просмотреть файл

@ -62,6 +62,9 @@ this.SystemMessagePermissionsTable = {
"settings": ["read", "write"]
},
"media-button": { },
"networkstats-alarm": {
"networkstats-manage": []
},
"notification": {
"desktop-notification": []
},

Просмотреть файл

@ -20,7 +20,22 @@ interface nsIDOMMozNetworkStatsInterface : nsISupports
readonly attribute DOMString id;
};
[scriptable, uuid(5f033d31-c9a2-4e2d-83aa-6a807f1e0c11)]
dictionary NetworkStatsAlarmOptions
{
jsval startTime; // Date object
jsval data;
};
[scriptable, builtinclass, uuid(063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c)]
interface nsIDOMMozNetworkStatsAlarm : nsISupports
{
readonly attribute unsigned long alarmId;
readonly attribute nsIDOMMozNetworkStatsInterface network;
readonly attribute long threshold;
readonly attribute jsval data;
};
[scriptable, uuid(50d109b8-0d7f-4208-81fe-5f07a759f159)]
interface nsIDOMMozNetworkStatsManager : nsISupports
{
/**
@ -42,6 +57,41 @@ interface nsIDOMMozNetworkStatsManager : nsISupports
in jsval end,
[optional] in DOMString manifestURL);
/**
* Install an alarm on a network. The network must be in the return of
* getAvailableNetworks() otherwise an "InvalidNetwork" exception will
* be raised.
*
* When total data usage reaches threshold bytes, a "networkstats-alarm"
* system message is sent to the application, where the optional parameter
* |data| must be a cloneable object.
*
* If success, the |result| field of the DOMRequest keeps the alarm Id.
*/
nsIDOMDOMRequest addAlarm(in nsIDOMMozNetworkStatsInterface network,
in long threshold,
[optional] in jsval options /* NetworkStatsAlarmOptions */);
/**
* Obtain all alarms for those networks returned by getAvailableNetworks().
* If a network is provided, only retrieves the alarms for that network.
* The network must be one of those returned by getAvailebleNetworks() or an
* "InvalidNetwork" exception will be raised.
*
* Each alarm object has the same fields as that in the system message:
* - alarmId
* - network
* - threshold
* - data
*/
nsIDOMDOMRequest getAllAlarms([optional] in nsIDOMMozNetworkStatsInterface network);
/**
* Remove all network alarms. If an |alarmId| is provided, then only that
* alarm is removed.
*/
nsIDOMDOMRequest removeAlarms([optional] in long alarmId);
/**
* Remove all stats related with the provided network from DB.
*/

Просмотреть файл

@ -16,8 +16,9 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.importGlobalProperties(["indexedDB"]);
const DB_NAME = "net_stats";
const DB_VERSION = 4;
const STORE_NAME = "net_stats";
const DB_VERSION = 5;
const STATS_STORE_NAME = "net_stats";
const ALARMS_STORE_NAME = "net_alarm";
// Constant defining the maximum values allowed per interface. If more, older
// will be erased.
@ -30,20 +31,20 @@ this.NetworkStatsDB = function NetworkStatsDB() {
if (DEBUG) {
debug("Constructor");
}
this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
this.initDBHelper(DB_NAME, DB_VERSION, [STATS_STORE_NAME, ALARMS_STORE_NAME]);
}
NetworkStatsDB.prototype = {
__proto__: IndexedDBHelper.prototype,
dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
dbNewTxn: function dbNewTxn(store_name, txn_type, callback, txnCb) {
function successCb(result) {
txnCb(null, result);
}
function errorCb(error) {
txnCb(error, null);
}
return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
return this.newTxn(txn_type, store_name, callback, successCb, errorCb);
},
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
@ -58,7 +59,7 @@ NetworkStatsDB.prototype = {
* Create the initial database schema.
*/
objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
objectStore.createIndex("connectionType", "connectionType", { unique: false });
objectStore.createIndex("timestamp", "timestamp", { unique: false });
objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
@ -77,9 +78,9 @@ NetworkStatsDB.prototype = {
// to modify the keyPath is mandatory to delete the object store
// and create it again. Old data is going to be deleted because the
// networkId for each sample can not be set.
db.deleteObjectStore(STORE_NAME);
db.deleteObjectStore(STATS_STORE_NAME);
objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
objectStore.createIndex("appId", "appId", { unique: false });
objectStore.createIndex("network", "network", { unique: false });
objectStore.createIndex("networkType", "networkType", { unique: false });
@ -94,7 +95,7 @@ NetworkStatsDB.prototype = {
}
} else if (currVersion == 3) {
// Delete redundent indexes (leave "network" only).
objectStore = aTransaction.objectStore(STORE_NAME);
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
if (objectStore.indexNames.contains("appId")) {
objectStore.deleteIndex("appId");
}
@ -120,18 +121,82 @@ NetworkStatsDB.prototype = {
if (DEBUG) {
debug("Deleted redundent indexes for version 4");
}
} else if (currVersion == 4) {
// In order to manage alarms, it is necessary to use a global counter
// (totalBytes) that will increase regardless of the system reboot.
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
// Now, systemBytes will hold the old totalBytes and totalBytes will
// keep the increasing counter. |counters| will keep the track of
// accumulated values.
let counters = {};
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (!cursor){
return;
}
cursor.value.rxSystemBytes = cursor.value.rxTotalBytes;
cursor.value.txSystemBytes = cursor.value.txTotalBytes;
if (cursor.value.appId == 0) {
let netId = cursor.value.network[0] + '' + cursor.value.network[1];
if (!counters[netId]) {
counters[netId] = {
rxCounter: 0,
txCounter: 0,
lastRx: 0,
lastTx: 0
};
}
let rxDiff = cursor.value.rxSystemBytes - counters[netId].lastRx;
let txDiff = cursor.value.txSystemBytes - counters[netId].lastTx;
if (rxDiff < 0 || txDiff < 0) {
// System reboot between samples, so take the current one.
rxDiff = cursor.value.rxSystemBytes;
txDiff = cursor.value.txSystemBytes;
}
counters[netId].rxCounter += rxDiff;
counters[netId].txCounter += txDiff;
cursor.value.rxTotalBytes = counters[netId].rxCounter;
cursor.value.txTotalBytes = counters[netId].txCounter;
counters[netId].lastRx = cursor.value.rxSystemBytes;
counters[netId].lastTx = cursor.value.txSystemBytes;
} else {
cursor.value.rxTotalBytes = cursor.value.rxSystemBytes;
cursor.value.txTotalBytes = cursor.value.txSystemBytes;
}
cursor.update(cursor.value);
cursor.continue();
};
// Create object store for alarms.
objectStore = db.createObjectStore(ALARMS_STORE_NAME, { keyPath: "id", autoIncrement: true });
objectStore.createIndex("alarm", ['networkId','threshold'], { unique: false });
objectStore.createIndex("manifestURL", "manifestURL", { unique: false });
if (DEBUG) {
debug("Created alarms store for version 5");
}
}
}
},
importData: function importData(aStats) {
let stats = { appId: aStats.appId,
network: [aStats.networkId, aStats.networkType],
timestamp: aStats.timestamp,
rxBytes: aStats.rxBytes,
txBytes: aStats.txBytes,
rxTotalBytes: aStats.rxTotalBytes,
txTotalBytes: aStats.txTotalBytes };
let stats = { appId: aStats.appId,
network: [aStats.networkId, aStats.networkType],
timestamp: aStats.timestamp,
rxBytes: aStats.rxBytes,
txBytes: aStats.txBytes,
rxSystemBytes: aStats.rxSystemBytes,
txSystemBytes: aStats.txSystemBytes,
rxTotalBytes: aStats.rxTotalBytes,
txTotalBytes: aStats.txTotalBytes };
return stats;
},
@ -160,18 +225,20 @@ NetworkStatsDB.prototype = {
saveStats: function saveStats(aStats, aResultCb) {
let timestamp = this.normalizeDate(aStats.date);
let stats = { appId: aStats.appId,
networkId: aStats.networkId,
networkType: aStats.networkType,
timestamp: timestamp,
rxBytes: (aStats.appId == 0) ? 0 : aStats.rxBytes,
txBytes: (aStats.appId == 0) ? 0 : aStats.txBytes,
rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
let stats = { appId: aStats.appId,
networkId: aStats.networkId,
networkType: aStats.networkType,
timestamp: timestamp,
rxBytes: (aStats.appId == 0) ? 0 : aStats.rxBytes,
txBytes: (aStats.appId == 0) ? 0 : aStats.txBytes,
rxSystemBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
txSystemBytes: (aStats.appId == 0) ? aStats.txBytes : 0,
rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
stats = this.importData(stats);
this.dbNewTxn("readwrite", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
if (DEBUG) {
debug("Filtered time: " + new Date(timestamp));
debug("New stats: " + JSON.stringify(stats));
@ -241,21 +308,29 @@ NetworkStatsDB.prototype = {
// |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
// Else, the incoming data is per-app data (|newSample.appId| is not 0),
// the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
let rxDiff = 0;
let txDiff = 0;
if (aNewSample.appId == 0) {
let rxDiff = aNewSample.rxTotalBytes - lastSample.rxTotalBytes;
let txDiff = aNewSample.txTotalBytes - lastSample.txTotalBytes;
rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
if (rxDiff < 0 || txDiff < 0) {
rxDiff = aNewSample.rxTotalBytes;
txDiff = aNewSample.txTotalBytes;
rxDiff = aNewSample.rxSystemBytes;
txDiff = aNewSample.txSystemBytes;
}
aNewSample.rxBytes = rxDiff;
aNewSample.txBytes = txDiff;
aNewSample.rxTotalBytes = lastSample.rxTotalBytes + rxDiff;
aNewSample.txTotalBytes = lastSample.txTotalBytes + txDiff;
} else {
rxDiff = aNewSample.rxBytes;
txDiff = aNewSample.txBytes;
}
if (diff == 1) {
// New element.
// If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
// If the incoming data is per-app data, new |rxTotalBytes|/|txTotalBytes|
// needs to be obtained by adding new |rxBytes|/|txBytes| to last
// |rxTotalBytes|/|txTotalBytes|.
if (aNewSample.appId != 0) {
@ -277,13 +352,15 @@ NetworkStatsDB.prototype = {
let data = [];
for (let i = diff - 2; i >= 0; i--) {
let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
let sample = { appId: aNewSample.appId,
network: aNewSample.network,
timestamp: time,
rxBytes: 0,
txBytes: 0,
rxTotalBytes: lastSample.rxTotalBytes,
txTotalBytes: lastSample.txTotalBytes };
let sample = { appId: aNewSample.appId,
network: aNewSample.network,
timestamp: time,
rxBytes: 0,
txBytes: 0,
rxSystemBytes: lastSample.rxSystemBytes,
txSystemBytes: lastSample.txSystemBytes,
rxTotalBytes: lastSample.rxTotalBytes,
txTotalBytes: lastSample.txTotalBytes };
data.push(sample);
}
@ -293,27 +370,20 @@ NetworkStatsDB.prototype = {
return;
}
if (diff == 0 || diff < 0) {
// New element received before samplerate period.
// It means that device has been restarted (or clock / timezone change).
// Update element.
// New element received before samplerate period. It means that device has
// been restarted (or clock / timezone change).
// Update element. If diff < 0, clock or timezone changed back. Place data
// in the last sample.
// If diff < 0, clock or timezone changed back. Place data in the last sample.
// Old |rxTotalBytes|/|txTotalBytes| needs to get updated by adding the
// last |rxTotalBytes|/|txTotalBytes|.
lastSample.rxBytes += rxDiff;
lastSample.txBytes += txDiff;
lastSample.rxSystemBytes = aNewSample.rxSystemBytes;
lastSample.txSystemBytes = aNewSample.txSystemBytes;
lastSample.rxTotalBytes += rxDiff;
lastSample.txTotalBytes += txDiff;
lastSample.rxBytes += aNewSample.rxBytes;
lastSample.txBytes += aNewSample.txBytes;
// If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
// needs to get updated by replacing the new |rxTotalBytes|/|txTotalBytes|.
if (aNewSample.appId == 0) {
lastSample.rxTotalBytes = aNewSample.rxTotalBytes;
lastSample.txTotalBytes = aNewSample.txTotalBytes;
} else {
// Else, the incoming data is per-app data, old |rxTotalBytes|/
// |txTotalBytes| needs to get updated by adding the new
// |rxBytes|/|txBytes| to last |rxTotalBytes|/|txTotalBytes|.
lastSample.rxTotalBytes += aNewSample.rxBytes;
lastSample.txTotalBytes += aNewSample.txBytes;
}
if (DEBUG) {
debug("Update: " + JSON.stringify(lastSample));
}
@ -379,7 +449,7 @@ NetworkStatsDB.prototype = {
let self = this;
// Clear and save an empty sample to keep sync with system counters
this.dbNewTxn("readwrite", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
let sample = null;
let request = aStore.index("network").openCursor(network, "prev");
request.onsuccess = function onsuccess(event) {
@ -431,6 +501,33 @@ NetworkStatsDB.prototype = {
this.clearInterfaceStats(aNetworks[index], callback);
},
getCurrentStats: function getCurrentStats(aNetwork, aDate, aResultCb) {
if (DEBUG) {
debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate);
}
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
let request = null;
let network = [aNetwork.id, aNetwork.type];
if (aDate) {
let start = this.normalizeDate(aDate);
let lowerFilter = [0, network, start];
let range = this.dbGlobal.IDBKeyRange.lowerBound(lowerFilter, false);
request = store.openCursor(range);
} else {
request = store.index("network").openCursor(network, "prev");
}
request.onsuccess = function onsuccess(event) {
txn.result = null;
let cursor = event.target.result;
if (cursor) {
txn.result = cursor.value;
}
};
}.bind(this), aResultCb);
},
find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
let start = this.normalizeDate(aStart);
@ -443,7 +540,7 @@ NetworkStatsDB.prototype = {
debug("End time: " + new Date(end));
}
this.dbNewTxn("readonly", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
let network = [aNetwork.id, aNetwork.type];
let lowerFilter = [aAppId, network, start];
let upperFilter = [aAppId, network, end];
@ -503,7 +600,7 @@ NetworkStatsDB.prototype = {
},
getAvailableNetworks: function getAvailableNetworks(aResultCb) {
this.dbNewTxn("readonly", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
if (!aTxn.result) {
aTxn.result = [];
}
@ -522,7 +619,7 @@ NetworkStatsDB.prototype = {
},
isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
this.dbNewTxn("readonly", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
if (!aTxn.result) {
aTxn.result = false;
}
@ -546,10 +643,159 @@ NetworkStatsDB.prototype = {
},
logAllRecords: function logAllRecords(aResultCb) {
this.dbNewTxn("readonly", function(aTxn, aStore) {
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
aStore.mozGetAll().onsuccess = function onsuccess(event) {
aTxn.result = event.target.result;
};
}, aResultCb);
},
alarmToRecord: function alarmToRecord(aAlarm) {
let record = { networkId: aAlarm.networkId,
threshold: aAlarm.threshold,
data: aAlarm.data,
manifestURL: aAlarm.manifestURL,
pageURL: aAlarm.pageURL };
if (aAlarm.id) {
record.id = aAlarm.id;
}
return record;
},
recordToAlarm: function recordToalarm(aRecord) {
let alarm = { networkId: aRecord.networkId,
threshold: aRecord.threshold,
data: aRecord.data,
manifestURL: aRecord.manifestURL,
pageURL: aRecord.pageURL };
if (aRecord.id) {
alarm.id = aRecord.id;
}
return alarm;
},
addAlarm: function addAlarm(aAlarm, aResultCb) {
this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
if (DEBUG) {
debug("Going to add " + JSON.stringify(aAlarm));
}
let record = this.alarmToRecord(aAlarm);
store.put(record).onsuccess = function setResult(aEvent) {
txn.result = aEvent.target.result;
if (DEBUG) {
debug("Request successful. New record ID: " + txn.result);
}
};
}.bind(this), aResultCb);
},
getFirstAlarm: function getFirstAlarm(aNetworkId, aResultCb) {
let self = this;
this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
if (DEBUG) {
debug("Get first alarm for network " + aNetworkId);
}
let lowerFilter = [aNetworkId, 0];
let upperFilter = [aNetworkId, ""];
let range = IDBKeyRange.bound(lowerFilter, upperFilter);
store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
let cursor = event.target.result;
txn.result = null;
if (cursor) {
txn.result = self.recordToAlarm(cursor.value);
}
};
}, aResultCb);
},
removeAlarm: function removeAlarm(aAlarmId, aManifestURL, aResultCb) {
this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
if (DEBUG) {
debug("Remove alarm " + aAlarmId);
}
store.get(aAlarmId).onsuccess = function onsuccess(event) {
let record = event.target.result;
txn.result = false;
if (!record || (aManifestURL && record.manifestURL != aManifestURL)) {
return;
}
store.delete(aAlarmId);
txn.result = true;
}
}, aResultCb);
},
removeAlarms: function removeAlarms(aManifestURL, aResultCb) {
this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
if (DEBUG) {
debug("Remove alarms of " + aManifestURL);
}
store.index("manifestURL").openCursor(aManifestURL)
.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
}
}, aResultCb);
},
updateAlarm: function updateAlarm(aAlarm, aResultCb) {
let self = this;
this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
if (DEBUG) {
debug("Update alarm " + aAlarm.id);
}
let record = self.alarmToRecord(aAlarm);
store.openCursor(record.id).onsuccess = function onsuccess(event) {
let cursor = event.target.result;
txn.result = false;
if (cursor) {
cursor.update(record);
txn.result = true;
}
}
}, aResultCb);
},
getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) {
this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
if (DEBUG) {
debug("Get alarms for " + aManifestURL);
}
txn.result = [];
store.index("manifestURL").openCursor(aManifestURL)
.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
if (!cursor) {
return;
}
if (!aNetworkId || cursor.value.networkId == aNetworkId) {
let alarm = { id: cursor.value.id,
networkId: cursor.value.networkId,
threshold: cursor.value.threshold,
data: cursor.value.data };
txn.result.push(alarm);
}
cursor.continue();
}
}, aResultCb);
}
};

Просмотреть файл

@ -30,7 +30,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
// NetworkStatsData
const nsIClassInfo = Ci.nsIClassInfo;
const NETWORKSTATSDATA_CID = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
const nsIDOMMozNetworkStatsData = Components.interfaces.nsIDOMMozNetworkStatsData;
const nsIDOMMozNetworkStatsData = Ci.nsIDOMMozNetworkStatsData;
function NetworkStatsData(aData) {
this.rxBytes = aData.rxBytes;
@ -40,10 +40,10 @@ function NetworkStatsData(aData) {
NetworkStatsData.prototype = {
__exposedProps__: {
rxBytes: 'r',
txBytes: 'r',
date: 'r',
},
rxBytes: 'r',
txBytes: 'r',
date: 'r',
},
classID : NETWORKSTATSDATA_CID,
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSDATA_CID,
@ -58,7 +58,7 @@ NetworkStatsData.prototype = {
// NetworkStatsInterface
const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
const NETWORKSTATSINTERFACE_CID = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
const nsIDOMMozNetworkStatsInterface = Components.interfaces.nsIDOMMozNetworkStatsInterface;
const nsIDOMMozNetworkStatsInterface = Ci.nsIDOMMozNetworkStatsInterface;
function NetworkStatsInterface(aNetwork) {
if (DEBUG) {
@ -70,9 +70,9 @@ function NetworkStatsInterface(aNetwork) {
NetworkStatsInterface.prototype = {
__exposedProps__: {
id: 'r',
type: 'r',
},
id: 'r',
type: 'r',
},
classID : NETWORKSTATSINTERFACE_CID,
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSINTERFACE_CID,
@ -87,7 +87,7 @@ NetworkStatsInterface.prototype = {
// NetworkStats
const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
const NETWORKSTATS_CID = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
const nsIDOMMozNetworkStats = Components.interfaces.nsIDOMMozNetworkStats;
const nsIDOMMozNetworkStats = Ci.nsIDOMMozNetworkStats;
function NetworkStats(aWindow, aStats) {
if (DEBUG) {
@ -106,12 +106,12 @@ function NetworkStats(aWindow, aStats) {
NetworkStats.prototype = {
__exposedProps__: {
manifestURL: 'r',
network: 'r',
start: 'r',
end: 'r',
data: 'r',
},
manifestURL: 'r',
network: 'r',
start: 'r',
end: 'r',
data: 'r',
},
classID : NETWORKSTATS_CID,
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATS_CID,
@ -125,11 +125,40 @@ NetworkStats.prototype = {
nsIDOMMozNetworkStatsInterface])
}
// NetworkStatsAlarm
const NETWORKSTATSALARM_CID = Components.ID("{063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}");
const nsIDOMMozNetworkStatsAlarm = Ci.nsIDOMMozNetworkStatsAlarm;
function NetworkStatsAlarm(aAlarm) {
this.alarmId = aAlarm.id;
this.network = new NetworkStatsInterface(aAlarm.network);
this.threshold = aAlarm.threshold;
this.data = aAlarm.data;
}
NetworkStatsAlarm.prototype = {
__exposedProps__: {
alarmId: 'r',
network: 'r',
threshold: 'r',
data: 'r',
},
classID : NETWORKSTATSALARM_CID,
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSALARM_CID,
contractID:"@mozilla.org/networkstatsalarm;1",
classDescription: "NetworkStatsAlarm",
interfaces: [nsIDOMMozNetworkStatsAlarm],
flags: nsIClassInfo.DOM_OBJECT}),
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsAlarm])
};
// NetworkStatsManager
const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
const NETWORKSTATSMANAGER_CID = Components.ID("{5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}");
const nsIDOMMozNetworkStatsManager = Components.interfaces.nsIDOMMozNetworkStatsManager;
const NETWORKSTATSMANAGER_CID = Components.ID("{50d109b8-0d7f-4208-81fe-5f07a759f159}");
const nsIDOMMozNetworkStatsManager = Ci.nsIDOMMozNetworkStatsManager;
function NetworkStatsManager() {
if (DEBUG) {
@ -189,6 +218,52 @@ NetworkStatsManager.prototype = {
return request;
},
addAlarm: function addAlarm(aNetwork, aThreshold, aOptions) {
this.checkPrivileges();
if (!aOptions) {
aOptions = Object.create(null);
}
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:SetAlarm",
{id: this.getRequestId(request),
data: {network: aNetwork,
threshold: aThreshold,
startTime: aOptions.startTime,
data: aOptions.data,
manifestURL: this.manifestURL,
pageURL: this.pageURL}});
return request;
},
getAllAlarms: function getAllAlarms(aNetwork) {
this.checkPrivileges();
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:GetAlarms",
{id: this.getRequestId(request),
data: {network: aNetwork,
manifestURL: this.manifestURL}});
return request;
},
removeAlarms: function removeAlarms(aAlarmId) {
this.checkPrivileges();
if (aAlarmId == 0) {
aAlarmId = -1;
}
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:RemoveAlarms",
{id: this.getRequestId(request),
data: {alarmId: aAlarmId,
manifestURL: this.manifestURL}});
return request;
},
getAvailableNetworks: function getAvailableNetworks() {
this.checkPrivileges();
@ -212,8 +287,8 @@ NetworkStatsManager.prototype = {
if (DEBUG) {
debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
}
let msg = aMessage.json;
let msg = aMessage.json;
let req = this.takeRequest(msg.id);
if (!req) {
if (DEBUG) {
@ -260,6 +335,30 @@ NetworkStatsManager.prototype = {
Services.DOMRequest.fireSuccess(req, true);
break;
case "NetworkStats:SetAlarm:Return":
case "NetworkStats:RemoveAlarms:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
Services.DOMRequest.fireSuccess(req, msg.result);
break;
case "NetworkStats:GetAlarms:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let alarms = Cu.createArrayIn(this._window);
for (let i = 0; i < msg.result.length; i++) {
alarms.push(new NetworkStatsAlarm(msg.result[i]));
}
Services.DOMRequest.fireSuccess(req, alarms);
break;
default:
if (DEBUG) {
debug("Wrong message: " + aMessage.name);
@ -293,7 +392,21 @@ NetworkStatsManager.prototype = {
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
"NetworkStats:GetAvailableNetworks:Return",
"NetworkStats:Clear:Return",
"NetworkStats:ClearAll:Return"]);
"NetworkStats:ClearAll:Return",
"NetworkStats:SetAlarm:Return",
"NetworkStats:GetAlarms:Return",
"NetworkStats:RemoveAlarms:Return"]);
// Init app properties.
let appsService = Cc["@mozilla.org/AppsService;1"]
.getService(Ci.nsIAppsService);
this.manifestURL = appsService.getManifestURLByLocalId(principal.appId);
let isApp = !!this.manifestURL.length;
if (isApp) {
this.pageURL = principal.URI.spec;
}
},
// Called from DOMRequestIpcHelper
@ -316,7 +429,8 @@ NetworkStatsManager.prototype = {
flags: nsIClassInfo.DOM_OBJECT})
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsAlarm,
NetworkStatsData,
NetworkStatsInterface,
NetworkStats,
NetworkStatsManager]);

Просмотреть файл

@ -7,6 +7,9 @@ contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
component {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11} NetworkStatsManager.js
contract @mozilla.org/networkStatsManager;1 {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}
component {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c} NetworkStatsManager.js
contract @mozilla.org/networkstatsalarm;1 {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}
component {50d109b8-0d7f-4208-81fe-5f07a759f159} NetworkStatsManager.js
contract @mozilla.org/networkStatsManager;1 {50d109b8-0d7f-4208-81fe-5f07a759f159}
category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1

Просмотреть файл

@ -22,6 +22,8 @@ Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
@ -50,6 +52,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
"@mozilla.org/settingsService;1",
"nsISettingsService");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
this.NetworkStatsService = {
init: function() {
debug("Service started");
@ -57,6 +63,7 @@ this.NetworkStatsService = {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
Services.obs.addObserver(this, "profile-after-change", false);
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
@ -87,6 +94,9 @@ this.NetworkStatsService = {
this.messages = ["NetworkStats:Get",
"NetworkStats:Clear",
"NetworkStats:ClearAll",
"NetworkStats:SetAlarm",
"NetworkStats:GetAlarms",
"NetworkStats:RemoveAlarms",
"NetworkStats:GetAvailableNetworks",
"NetworkStats:SampleRate",
"NetworkStats:MaxStorageAge"];
@ -107,6 +117,9 @@ this.NetworkStatsService = {
this.updateQueue = [];
this.isQueueRunning = false;
this._currentAlarms = {};
this.initAlarms();
},
receiveMessage: function(aMessage) {
@ -129,6 +142,15 @@ this.NetworkStatsService = {
case "NetworkStats:ClearAll":
this.clearDB(mm, msg);
break;
case "NetworkStats:SetAlarm":
this.setAlarm(mm, msg);
break;
case "NetworkStats:GetAlarms":
this.getAlarms(mm, msg);
break;
case "NetworkStats:RemoveAlarms":
this.removeAlarms(mm, msg);
break;
case "NetworkStats:GetAvailableNetworks":
this.getAvailableNetworks(mm, msg);
break;
@ -146,7 +168,7 @@ this.NetworkStatsService = {
case TOPIC_INTERFACE_REGISTERED:
case TOPIC_INTERFACE_UNREGISTERED:
// If new interface is registered (notified from NetworkManager),
// If new interface is registered (notified from NetworkService),
// the stats are updated for the new interface without waiting to
// complete the updating period.
@ -158,9 +180,27 @@ this.NetworkStatsService = {
break;
}
this._updateCurrentAlarm(netId);
debug("NetId: " + netId);
this.updateStats(netId);
break;
case TOPIC_BANDWIDTH_CONTROL:
debug("Bandwidth message from netd: " + JSON.stringify(aData));
let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
for (let networkId in this._networks) {
if (interfaceName == this._networks[networkId].interfaceName) {
let currentAlarm = this._currentAlarms[networkId];
if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
this._fireAlarm(currentAlarm.alarm);
}
break;
}
}
break;
case "xpcom-shutdown":
debug("Service shutdown");
@ -172,6 +212,7 @@ this.NetworkStatsService = {
Services.obs.removeObserver(this, "profile-after-change");
Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
this.timer.cancel();
this.timer = null;
@ -266,6 +307,25 @@ this.NetworkStatsService = {
});
},
initAlarms: function initAlarms() {
debug("Init usage alarms");
let self = this;
for (let netId in this._networks) {
this._currentAlarms[netId] = Object.create(null);
this._db.getFirstAlarm(netId, function getResult(error, result) {
if (!error && result) {
self._setAlarm(result, function onSet(error, success) {
if (error == "InvalidStateError") {
self._fireAlarm(result);
}
});
}
});
}
},
/*
* Function called from manager to get stats from database.
* In order to return updated stats, first is performed a call to
@ -482,8 +542,8 @@ this.NetworkStatsService = {
}
} else {
// The caller is a function that has pushed new elements to the queue,
// if isQueueRunning is false it means there is no processing currently being
// done, so start.
// if isQueueRunning is false it means there is no processing currently
// being done, so start.
if (this.isQueueRunning) {
if(this.updateQueue.length > 1) {
return;
@ -515,7 +575,7 @@ this.NetworkStatsService = {
let interfaceName = this._networks[aNetId].interfaceName;
debug("Update stats for " + interfaceName);
// Request stats to NetworkManager, which will get stats from netd, passing
// Request stats to NetworkService, which will get stats from netd, passing
// 'networkStatsAvailable' as a callback.
if (interfaceName) {
networkService.getNetworkInterfaceStats(interfaceName,
@ -706,6 +766,262 @@ this.NetworkStatsService = {
debug(JSON.stringify(aResult));
});
},
getAlarms: function getAlarms(mm, msg) {
let network = msg.data.network;
let manifestURL = msg.data.manifestURL;
let netId = null;
if (network) {
netId = this.getNetworkId(network.id, network.type);
if (!this._networks[netId]) {
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
{ id: msg.id, error: "InvalidInterface", result: null });
return;
}
}
let self = this;
this._db.getAlarms(netId, manifestURL, function onCompleted(error, result) {
if (error) {
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
{ id: msg.id, error: error, result: result });
return;
}
let alarms = []
// NetworkStatsManager must return the network instead of the networkId.
for (let i = 0; i < result.length; i++) {
let alarm = result[i];
alarms.push({ id: alarm.id,
network: self._networks[alarm.networkId].network,
threshold: alarm.threshold,
data: alarm.data });
}
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
{ id: msg.id, error: null, result: alarms });
});
},
removeAlarms: function removeAlarms(mm, msg) {
let alarmId = msg.data.alarmId;
let manifestURL = msg.data.manifestURL;
let self = this;
let callback = function onRemove(error, result) {
if (error) {
mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
{ id: msg.id, error: error, result: result });
return;
}
for (let i in self._currentAlarms) {
let currentAlarm = self._currentAlarms[i].alarm;
if (currentAlarm && ((alarmId == currentAlarm.id) ||
(alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
self._updateCurrentAlarm(currentAlarm.networkId);
}
}
mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
{ id: msg.id, error: error, result: true });
};
if (alarmId == -1) {
this._db.removeAlarms(manifestURL, callback);
} else {
this._db.removeAlarm(alarmId, manifestURL, callback);
}
},
/*
* Function called from manager to set an alarm.
*/
setAlarm: function setAlarm(mm, msg) {
let options = msg.data;
let network = options.network;
let threshold = options.threshold;
debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
if (threshold < 0) {
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
{ id: msg.id, error: "InvalidThresholdValue", result: null });
return;
}
let netId = this.getNetworkId(network.id, network.type);
if (!this._networks[netId]) {
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
{ id: msg.id, error: "InvalidiConnectionType", result: null });
return;
}
let newAlarm = {
id: null,
networkId: netId,
threshold: threshold,
absoluteThreshold: null,
startTime: options.startTime,
data: options.data,
pageURL: options.pageURL,
manifestURL: options.manifestURL
};
let self = this;
this._updateThreshold(newAlarm, function onUpdate(error, _threshold) {
if (error) {
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
{ id: msg.id, error: error, result: null });
return;
}
newAlarm.absoluteThreshold = _threshold.absoluteThreshold;
self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
if (error) {
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
{ id: msg.id, error: error, result: null });
return;
}
newAlarm.id = newId;
self._setAlarm(newAlarm, function onSet(error, success) {
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
{ id: msg.id, error: error, result: newId });
if (error == "InvalidStateError") {
self._fireAlarm(newAlarm);
}
});
});
});
},
_setAlarm: function _setAlarm(aAlarm, aCallback) {
let currentAlarm = this._currentAlarms[aAlarm.networkId];
if (Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) {
aCallback(null, true);
return;
}
let self = this;
this._updateThreshold(aAlarm, function onUpdate(aError, aThreshold) {
if (aError) {
aCallback(aError, null);
return;
}
let callback = function onAlarmSet(aError) {
if (aError) {
debug("Set alarm error: " + aError);
aCallback("netdError", null);
return;
}
self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
aCallback(null, true);
};
debug("Set alarm " + JSON.stringify(aAlarm));
let interfaceName = self._networks[aAlarm.networkId].interfaceName;
if (interfaceName) {
networkService.setNetworkInterfaceAlarm(interfaceName,
aThreshold.systemThreshold,
callback);
return;
}
aCallback(null, true);
});
},
_updateThreshold: function _updateThreshold(aAlarm, aCallback) {
let self = this;
this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
aAlarm.startTime,
function onStatsFound(error, result) {
if (error) {
debug("Error getting stats for " +
JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
aCallback(error, result);
return;
}
let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes;
// Alarm set to a threshold lower than current rx/tx bytes.
if (offset <= 0) {
aCallback("InvalidStateError", null);
return;
}
let threshold = {
systemThreshold: result.rxSystemBytes + result.txSystemBytes + offset,
absoluteThreshold: result.rxTotalBytes + result.txTotalBytes + offset
};
aCallback(null, threshold);
});
});
},
_fireAlarm: function _fireAlarm(aAlarm) {
debug("Fire alarm");
let self = this;
this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
if (!aError && !aResult) {
return;
}
self._fireSystemMessage(aAlarm);
self._updateCurrentAlarm(aAlarm.networkId);
});
},
_updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
this._currentAlarms[aNetworkId] = Object.create(null);
let self = this;
this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
if (error) {
debug("Error getting the first alarm");
return;
}
if (!result) {
let interfaceName = self._networks[aNetworkId].interfaceName;
networkService.setNetworkInterfaceAlarm(interfaceName, -1,
function onComplete(){});
return;
}
self._setAlarm(result, function onSet(error, success){
if (error == "InvalidStateError") {
self._fireAlarm(result);
return;
}
});
});
},
_fireSystemMessage: function _fireSystemMessage(aAlarm) {
debug("Fire system message: " + JSON.stringify(aAlarm));
let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
let alarm = { "id": aAlarm.id,
"threshold": aAlarm.threshold,
"data": aAlarm.data };
messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
}
};
NetworkStatsService.init();

Просмотреть файл

@ -15,5 +15,6 @@ MOCHITEST_FILES = \
test_networkstats_disabled.html \
test_networkstats_enabled_no_perm.html \
test_networkstats_enabled_perm.html \
test_networkstats_alarms.html \
$(NULL)
endif

Просмотреть файл

@ -0,0 +1,175 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for NetworkStats alarms</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content">
</div>
<pre id="test">
<script type="application/javascript">
function test() {
ok(true, "Checking if no alarms are set.");
req = navigator.mozNetworkStats.getAllAlarms();
req.onsuccess = function () {
ok(true, "Succeeded to get alarms.");
ok(Array.isArray(req.result) && req.result.length == 0,
"There are no alarms set.");
next();
};
req.onerror = function () {
ok(false, "getAllAlarms() shouldn't fail!");
}
}
var req;
var index = -1;
var wifi = {'type': 0, 'id': '0'};
var mobile = {'type': 1, 'id': '1'};
var steps = [
function () {
ok(true, "Calling getAllAlarms() with invalid network.");
req = navigator.mozNetworkStats.getAllAlarms(mobile);
req.onsuccess = function () {
ok(false, "getAllAlarms() shouldn't succeed!");
};
req.onerror = function () {
ok(req.error.name == "InvalidInterface", "Get InvalidInterface error");
next();
}
},
function () {
ok(true, "Calling addAlarm() with invalid network or parameters.");
try {
navigator.mozNetworkStats.addAlarm();
} catch(ex) {
ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
"addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no parameters");
}
try {
navigator.mozNetworkStats.addAlarm(100000);
} catch(ex) {
ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
"addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no network");
}
try {
navigator.mozNetworkStats.addAlarm(wifi);
} catch(ex) {
ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
"addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no threshold");
}
req = navigator.mozNetworkStats.addAlarm(mobile, -100000);
req.onsuccess = function () {
ok(false, "addAlarm() shouldn't succeed with negative threshold.");
};
req.onerror = function () {
ok(req.error.name == "InvalidThresholdValue", "Get InvalidThresholdValue error");
next();
}
},
function () {
ok(true, "Calling addAlarm()");
req = navigator.mozNetworkStats.addAlarm(wifi, 1000000);
req.onsuccess = function () {
ok(true, "Succeeded to add alarm. AlarmId: " + req.result);
next();
};
req.onerror = function () {
ok(false, "addAlarm() shouldn't fail.");
};
},
function () {
ok(true, "Calling getAllAlarms()");
req = navigator.mozNetworkStats.getAllAlarms(wifi);
req.onsuccess = function () {
ok(req.result.length == 1, "Only one alarm");
ok(req.result[0].alarmId == 1, "Get correct alarmId");
next();
};
req.onerror = function () {
ok(false, "getAllAlarms() shouldn't fail.");
}
},
function () {
ok(true, "Calling removeAlarms() to remove alarms.");
req = navigator.mozNetworkStats.removeAlarms();
req.onsuccess = function () {
ok(req.result, "Succeeded to remove alarms.");
next();
};
req.onerror = function () {
ok(false, "removeAlarms() shouldn't fail.");
}
},
function () {
ok(true, "Checking if all alarms are removed.");
req = navigator.mozNetworkStats.getAllAlarms();
req.onsuccess = function () {
ok(Array.isArray(req.result) && req.result.length == 0,
"Succeeded to remove all alarms.");
next();
};
req.onerror = function () {
ok(false, "getAllAlarms() shouldn't fail.");
}
},
function () {
ok(true, "all done!\n");
SpecialPowers.removePermission("networkstats-manage", document);
SimpleTest.finish();
return;
}
];
function next() {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.addPermission("networkstats-manage", true, document);
SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -13,10 +13,8 @@
<script type="application/javascript">
function test() {
ok('mozNetworkStats' in navigator, "navigator.mozMozNetworkStats should exist");
ok(navigator.mozNetworkStats, "navigator.mozNetworkStats returns an object");
netStats = navigator.mozNetworkStats;
netStats = window.navigator.mozNetworkStats;
ok(netStats, "mozNetworkStats exists");
// Test IDL attributes
ok('sampleRate' in netStats,

Просмотреть файл

@ -7,12 +7,28 @@ Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
const netStatsDb = new NetworkStatsDB();
function clearWholeDB(callback) {
netStatsDb.dbNewTxn("readwrite", function(aTxn, aStore) {
aStore.delete();
function clearStore(store, callback) {
netStatsDb.dbNewTxn(store, "readwrite", function(aTxn, aStore) {
aStore.openCursor().onsuccess = function (event) {
let cursor = event.target.result;
if (cursor){
cursor.delete();
cursor.continue();
}
};
}, callback);
}
add_test(function prepareDatabase() {
// Clear whole database to avoid starting tests with unknown state
// due to the previous tests.
clearStore('net_stats', function() {
clearStore('net_alarm', function() {
run_next_test();
});
});
});
function filterTimestamp(date) {
var sampleRate = netStatsDb.sampleRate;
var offset = date.getTimezoneOffset() * 60 * 1000;
@ -122,15 +138,17 @@ add_test(function test_clear_interface() {
add_test(function test_internalSaveStats_singleSample() {
var networks = getNetworks();
var stats = { appId: 0,
network: [networks[0].id, networks[0].type],
timestamp: Date.now(),
rxBytes: 0,
txBytes: 0,
rxTotalBytes: 1234,
txTotalBytes: 1234 };
var stats = { appId: 0,
network: [networks[0].id, networks[0].type],
timestamp: Date.now(),
rxBytes: 0,
txBytes: 0,
rxSystemBytes: 1234,
txSystemBytes: 1234,
rxTotalBytes: 1234,
txTotalBytes: 1234 };
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
@ -143,6 +161,8 @@ add_test(function test_internalSaveStats_singleSample() {
do_check_eq(result[0].timestamp, stats.timestamp);
do_check_eq(result[0].rxBytes, stats.rxBytes);
do_check_eq(result[0].txBytes, stats.txBytes);
do_check_eq(result[0].rxSystemBytes, stats.rxSystemBytes);
do_check_eq(result[0].txSystemBytes, stats.txSystemBytes);
do_check_eq(result[0].rxTotalBytes, stats.rxTotalBytes);
do_check_eq(result[0].txTotalBytes, stats.txTotalBytes);
run_next_test();
@ -161,16 +181,18 @@ add_test(function test_internalSaveStats_arraySamples() {
var samples = 2;
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: 0,
network: network,
timestamp: Date.now() + (10 * i),
rxBytes: 0,
txBytes: 0,
rxTotalBytes: 1234,
txTotalBytes: 1234 });
stats.push({ appId: 0,
network: network,
timestamp: Date.now() + (10 * i),
rxBytes: 0,
txBytes: 0,
rxSystemBytes: 1234,
txSystemBytes: 1234,
rxTotalBytes: 1234,
txTotalBytes: 1234 });
}
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
@ -182,7 +204,6 @@ add_test(function test_internalSaveStats_arraySamples() {
// an empty sample to keep totalBytes synchronized with netd counters
result.shift();
do_check_eq(result.length, samples);
var success = true;
for (var i = 1; i < samples; i++) {
if (result[i].appId != stats[i].appId ||
@ -190,6 +211,8 @@ add_test(function test_internalSaveStats_arraySamples() {
result[i].timestamp != stats[i].timestamp ||
result[i].rxBytes != stats[i].rxBytes ||
result[i].txBytes != stats[i].txBytes ||
result[i].rxSystemBytes != stats[i].rxSystemBytes ||
result[i].txSystemBytes != stats[i].txSystemBytes ||
result[i].rxTotalBytes != stats[i].rxTotalBytes ||
result[i].txTotalBytes != stats[i].txTotalBytes) {
success = false;
@ -213,18 +236,20 @@ add_test(function test_internalRemoveOldStats() {
var samples = 10;
var stats = [];
for (var i = 0; i < samples - 1; i++) {
stats.push({ appId: 0,
network: network, timestamp: Date.now() + (10 * i),
rxBytes: 0, txBytes: 0,
rxTotalBytes: 1234, txTotalBytes: 1234 });
stats.push({ appId: 0,
network: network, timestamp: Date.now() + (10 * i),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 1234, txTotalBytes: 1234 });
}
stats.push({ appId: 0,
network: network, timestamp: Date.now() + (10 * samples),
rxBytes: 0, txBytes: 0,
rxTotalBytes: 1234, txTotalBytes: 1234 });
stats.push({ appId: 0,
network: network, timestamp: Date.now() + (10 * samples),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 1234, txTotalBytes: 1234 });
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
var date = stats[stats.length - 1].timestamp
+ (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
@ -245,10 +270,10 @@ add_test(function test_internalRemoveOldStats() {
function processSamplesDiff(networks, lastStat, newStat, callback) {
netStatsDb.clearStats(networks, function (error, result){
do_check_eq(error, null);
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, lastStat);
}, function(error, result) {
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
let request = store.index("network").openCursor(newStat.network, "prev");
request.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
@ -273,25 +298,29 @@ add_test(function test_processSamplesDiffSameSample() {
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxTotalBytes: 1234, txTotalBytes: 1234 };
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 2234, txSystemBytes: 2234,
rxTotalBytes: 2234, txTotalBytes: 2234 };
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 1);
do_check_eq(result[0].appId, newStat.appId);
do_check_true(compareNetworks(result[0].network, newStat.network));
do_check_eq(result[0].timestamp, newStat.timestamp);
do_check_eq(result[0].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
do_check_eq(result[0].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
do_check_eq(result[0].rxTotalBytes, newStat.rxTotalBytes);
do_check_eq(result[0].txTotalBytes, newStat.txTotalBytes);
do_check_eq(result[0].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
do_check_eq(result[0].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
do_check_eq(result[0].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
do_check_eq(result[0].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
do_check_eq(result[0].rxSystemBytes, newStat.rxSystemBytes);
do_check_eq(result[0].txSystemBytes, newStat.txSystemBytes);
run_next_test();
});
});
@ -303,25 +332,29 @@ add_test(function test_processSamplesDiffNextSample() {
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxTotalBytes: 1234, txTotalBytes: 1234 };
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
network: network, timestamp: date + sampleRate,
rxBytes: 0, txBytes: 0,
rxTotalBytes: 500, txTotalBytes: 500 };
var newStat = { appId: 0,
network: network, timestamp: date + sampleRate,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1734, txSystemBytes: 1734,
rxTotalBytes: 0, txTotalBytes: 0 };
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 2);
do_check_eq(result[1].appId, newStat.appId);
do_check_true(compareNetworks(result[1].network, newStat.network));
do_check_eq(result[1].timestamp, newStat.timestamp);
do_check_eq(result[1].rxBytes, newStat.rxTotalBytes);
do_check_eq(result[1].txBytes, newStat.txTotalBytes);
do_check_eq(result[1].rxTotalBytes, newStat.rxTotalBytes);
do_check_eq(result[1].txTotalBytes, newStat.txTotalBytes);
do_check_eq(result[1].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
do_check_eq(result[1].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
do_check_eq(result[1].rxSystemBytes, newStat.rxSystemBytes);
do_check_eq(result[1].txSystemBytes, newStat.txSystemBytes);
do_check_eq(result[1].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
do_check_eq(result[1].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
run_next_test();
});
});
@ -332,15 +365,17 @@ add_test(function test_processSamplesDiffSamplesLost() {
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxTotalBytes: 1234, txTotalBytes: 1234 };
var lastStat = { appId: 0,
network: network, timestamp: date,
rxBytes: 0, txBytes: 0,
rxSystemBytes: 1234, txSystemBytes: 1234,
rxTotalBytes: 2234, txTotalBytes: 2234};
var newStat = { appId: 0,
network: network, timestamp: date + (sampleRate * samples),
rxBytes: 0, txBytes: 0,
rxTotalBytes: 2234, txTotalBytes: 2234 };
var newStat = { appId: 0,
network: network, timestamp: date + (sampleRate * samples),
rxBytes: 0, txBytes: 0,
rxSystemBytes: 2234, txSystemBytes: 2234,
rxTotalBytes: 0, txTotalBytes: 0 };
processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, samples + 1);
@ -349,8 +384,10 @@ add_test(function test_processSamplesDiffSamplesLost() {
do_check_eq(result[samples].timestamp, newStat.timestamp);
do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
do_check_eq(result[samples].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
do_check_eq(result[samples].rxTotalBytes, newStat.rxTotalBytes);
do_check_eq(result[samples].txTotalBytes, newStat.txTotalBytes);
do_check_eq(result[samples].rxSystemBytes, newStat.rxSystemBytes);
do_check_eq(result[samples].txSystemBytes, newStat.txSystemBytes);
do_check_eq(result[samples].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
do_check_eq(result[samples].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
run_next_test();
});
});
@ -366,8 +403,7 @@ add_test(function test_saveStats() {
rxBytes: 2234,
txBytes: 2234};
netStatsDb.clearStats(networks, function (error, result) {
do_check_eq(error, null);
clearStore('net_stats', function() {
netStatsDb.saveStats(stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.logAllRecords(function(error, result) {
@ -377,8 +413,10 @@ add_test(function test_saveStats() {
do_check_true(compareNetworks(result[0].network, network));
let timestamp = filterTimestamp(stats.date);
do_check_eq(result[0].timestamp, timestamp);
do_check_eq(result[0].rxBytes, 0);
do_check_eq(result[0].txBytes, 0);
do_check_eq(result[0].rxBytes, stats.rxBytes);
do_check_eq(result[0].txBytes, stats.txBytes);
do_check_eq(result[0].rxSystemBytes, stats.rxBytes);
do_check_eq(result[0].txSystemBytes, stats.txBytes);
do_check_eq(result[0].rxTotalBytes, stats.rxBytes);
do_check_eq(result[0].txTotalBytes, stats.txBytes);
run_next_test();
@ -416,6 +454,8 @@ add_test(function test_saveAppStats() {
do_check_eq(result[1].timestamp, timestamp);
do_check_eq(result[1].rxBytes, stats.rxBytes);
do_check_eq(result[1].txBytes, stats.txBytes);
do_check_eq(result[1].rxSystemBytes, 0);
do_check_eq(result[1].txSystemBytes, 0);
do_check_eq(result[1].rxTotalBytes, 0);
do_check_eq(result[1].txTotalBytes, 0);
run_next_test();
@ -427,7 +467,7 @@ add_test(function test_saveAppStats() {
function prepareFind(network, stats, callback) {
netStatsDb.clearStats(network, function (error, result) {
do_check_eq(error, null);
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
callback(error, result);
@ -449,15 +489,18 @@ add_test(function test_find () {
start = new Date(start - sampleRate);
var stats = [];
for (var i = 0; i < samples; i++) {
stats.push({ appId: appId,
network: networkWifi, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
stats.push({ appId: appId,
network: networkWifi, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxSystemBytes: 0, txSystemBytes: 0,
rxTotalBytes: 0, txTotalBytes: 0});
stats.push({ appId: appId,
network: networkMobile, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxTotalBytes: 0, txTotalBytes: 0 });
stats.push({ appId: appId,
network: networkMobile, timestamp: saveDate + (sampleRate * i),
rxBytes: 0, txBytes: 10,
rxSystemBytes: 0, txSystemBytes: 0,
rxTotalBytes: 0, txTotalBytes: 0});
}
prepareFind(networks[0], stats, function(error, result) {
@ -554,21 +597,22 @@ add_test(function test_saveMultipleAppStats () {
if (index == keys.length - 1) {
netStatsDb.logAllRecords(function(error, result) {
// Again, result has two samples more than expected samples because
// clear inserts one empty sample for each network to keep totalBytes
// synchronized with netd counters. so the first two samples have to
// be discarted.
result.shift();
result.shift();
// Again, result has two samples more than expected samples because
// clear inserts one empty sample for each network to keep totalBytes
// synchronized with netd counters. so the first two samples have to
// be discarted.
result.shift();
result.shift();
do_check_eq(error, null);
do_check_eq(result.length, 4);
do_check_eq(result[0].appId, 1);
do_check_true(compareNetworks(result[0].network,[networkWifi.id, networkWifi.type]));
do_check_eq(result[0].rxBytes, 0);
do_check_eq(result[0].txBytes, 10);
run_next_test();
do_check_eq(error, null);
do_check_eq(result.length, 4);
do_check_eq(result[0].appId, 1);
do_check_true(compareNetworks(result[0].network, [networkWifi.id, networkWifi.type]));
do_check_eq(result[0].rxBytes, 0);
do_check_eq(result[0].txBytes, 10);
run_next_test();
});
return;
}
index += 1;
@ -577,12 +621,188 @@ add_test(function test_saveMultipleAppStats () {
});
});
var networkWifi = '00';
var networkMobile = '11';
var examplePageURL = "http://example.com/index.html";
var exampleManifestURL = "http://example.com/manifest.webapp";
var testPageURL = "http://test.com/index.html";
var testManifestURL = "http://test.com/manifest.webapp";
var alarms = [{ id: null,
networkId: networkWifi,
threshold: 10000,
data: {foo: "something"},
pageURL: examplePageURL,
manifestURL: exampleManifestURL },
{ id: null,
networkId: networkWifi,
threshold: 1000,
data: {foo: "else"},
pageURL: examplePageURL,
manifestURL: exampleManifestURL },
{ id: null,
networkId: networkMobile,
threshold: 100,
data: {foo: "to"},
pageURL: examplePageURL,
manifestURL: exampleManifestURL },
{ id: null,
networkId: networkMobile,
threshold: 10,
data: {foo: "test"},
pageURL: testPageURL,
manifestURL: testManifestURL }];
var alarmsDbId = 1;
add_test(function test_addAlarm() {
// Add alarms[0] -> DB: [ alarms[0] (id: 1) ]
// Check the insertion is OK.
netStatsDb.addAlarm(alarms[0], function(error, result) {
do_check_eq(error, null);
alarmsDbId = result;
netStatsDb.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
do_check_eq(result[0].id, alarmsDbId);
do_check_eq(result[0].networkId, alarms[0].networkId);
do_check_eq(result[0].threshold, alarms[0].threshold);
do_check_eq(result[0].data.foo, alarms[0].data.foo);
run_next_test();
});
});
});
add_test(function test_getFirstAlarm() {
// Add alarms[1] -> DB: [ alarms[0] (id: 1), alarms[1] (id: 2) ]
// Check first alarm is alarms[1] because threshold is lower.
alarmsDbId += 1;
netStatsDb.addAlarm(alarms[1], function (error, result) {
do_check_eq(error, null);
do_check_eq(result, alarmsDbId);
netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.id, alarmsDbId);
do_check_eq(result.networkId, alarms[1].networkId);
do_check_eq(result.threshold, alarms[1].threshold);
do_check_eq(result.data.foo, alarms[1].data.foo);
do_check_eq(result.pageURL, alarms[1].pageURL);
do_check_eq(result.manifestURL, alarms[1].manifestURL);
run_next_test();
});
});
});
add_test(function test_removeAlarm() {
// Remove alarms[1] (id: 2) -> DB: [ alarms[0] (id: 1) ]
// Check get first return alarms[0].
netStatsDb.removeAlarm(alarmsDbId, alarms[0].manifestURL, function (error, result) {
do_check_eq(error, null);
netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.id, alarmsDbId - 1);
do_check_eq(result.networkId, alarms[0].networkId);
do_check_eq(result.threshold, alarms[0].threshold);
do_check_eq(result.data.foo, alarms[0].data.foo);
do_check_eq(result.pageURL, alarms[0].pageURL);
do_check_eq(result.manifestURL, alarms[0].manifestURL);
run_next_test();
});
});
});
add_test(function test_removeAppAlarm() {
// Remove alarms[0] (id: 1) -> DB: [ ]
netStatsDb.removeAlarm(alarmsDbId - 1, alarms[0].manifestURL, function (error, result) {
do_check_eq(error, null);
netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 0);
run_next_test();
});
});
});
add_test(function test_getAlarms() {
// Add all alarms -> DB: [ alarms[0] (id: 3),
// alarms[1] (id: 4),
// alarms[2] (id: 5),
// alarms[3] (id: 6) ]
// Check that getAlarms for wifi returns 2 alarms.
// Check that getAlarms for all connections returns 3 alarms.
var callback = function () {
netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 2);
netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 3);
run_next_test();
});
});
};
var index = 0;
var addFunction = function () {
alarmsDbId += 1;
netStatsDb.addAlarm(alarms[index], function (error, result) {
do_check_eq(error, null);
index += 1;
do_check_eq(result, alarmsDbId);
if (index >= alarms.length) {
callback();
return;
}
addFunction();
});
};
addFunction();
});
add_test(function test_removeAppAllAlarms() {
// Remove all alarms for exampleManifestURL -> DB: [ alarms[3] (id: 6) ]
netStatsDb.removeAlarms(exampleManifestURL, function (error, result) {
do_check_eq(error, null);
netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 0);
netStatsDb.getAlarms(null, testManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
run_next_test();
});
});
});
});
add_test(function test_updateAlarm() {
// Update alarms[3] (id: 6) -> DB: [ alarms[3]* (id: 6) ]
var updatedAlarm = alarms[1];
updatedAlarm.id = alarmsDbId;
updatedAlarm.threshold = 10;
netStatsDb.updateAlarm(updatedAlarm, function (error, result) {
do_check_eq(error, null);
netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.id, updatedAlarm.id);
do_check_eq(result.networkId, updatedAlarm.networkId);
do_check_eq(result.threshold, updatedAlarm.threshold);
do_check_eq(result.data.foo, updatedAlarm.data.foo);
do_check_eq(result.pageURL, updatedAlarm.pageURL);
do_check_eq(result.manifestURL, updatedAlarm.manifestURL);
run_next_test();
});
});
});
function run_test() {
do_get_profile();
// Clear whole database to avoid start tests with unknown state
// due to previous tests.
clearWholeDB(function(){
run_next_test();
});
run_next_test();
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше