This commit is contained in:
Ryan VanderMeulen 2014-12-05 19:10:22 -05:00
Родитель 5f0aa6245d de9c316b20
Коммит 5e0f31c0c5
64 изменённых файлов: 940 добавлений и 121 удалений

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

@ -1221,27 +1221,18 @@ toolbarpaletteitem[place="palette"][hidden] {
animation-duration: 2s; animation-duration: 2s;
} }
#abouthome-search-panel .panel-arrowcontent { #abouthome-search-panel > .panel-arrowcontainer > .panel-arrowcontent {
-moz-padding-start: 0; padding: 0;
-moz-padding-end: 0;
padding-top: 0;
padding-bottom: 0;
background: rgb(248, 250, 251);
font-size: 110%; font-size: 110%;
} }
.abouthome-search-panel-item { #abouthome-search-panel-manage {
-moz-box-align: center; padding: 4px 24px;
padding-top: 4px;
padding-bottom: 4px;
-moz-padding-start: 24px;
-moz-padding-end: 24px;
} }
.abouthome-search-panel-item > label { #abouthome-search-panel-manage > label {
-moz-padding-start: 0; padding: 0;
-moz-margin-start: 0; margin: 0;
color: rgb(130, 132, 133);
} }
/* Combined context-menu items */ /* Combined context-menu items */

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

@ -278,7 +278,7 @@
<panel id="abouthome-search-panel" orient="vertical" type="arrow" hidden="true" <panel id="abouthome-search-panel" orient="vertical" type="arrow" hidden="true"
onclick="this.hidePopup()"> onclick="this.hidePopup()">
<hbox id="abouthome-search-panel-manage" class="abouthome-search-panel-item" <hbox id="abouthome-search-panel-manage"
onclick="openPreferences('paneSearch')"> onclick="openPreferences('paneSearch')">
<label>&changeSearchSettings.button;</label> <label>&changeSearchSettings.button;</label>
</hbox> </hbox>

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

@ -454,27 +454,20 @@ input[type=button] {
transition-duration: 0ms; transition-duration: 0ms;
} }
#newtab-customize-panel .panel-arrowcontent, #newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent,
#newtab-search-panel .panel-arrowcontent { #newtab-search-panel > .panel-arrowcontainer > .panel-arrowcontent {
-moz-padding-start: 0; padding: 0;
-moz-padding-end: 0;
padding-top: 0;
padding-bottom: 0;
background: rgb(248, 250, 251);
} }
.newtab-customize-panel-item, .newtab-customize-panel-item,
.newtab-search-panel-engine { .newtab-search-panel-engine,
-moz-box-align: center; #newtab-search-manage {
padding-top: 4px; padding: 4px 24px;
padding-bottom: 4px;
-moz-padding-start: 24px;
-moz-padding-end: 24px;
} }
.newtab-customize-panel-item:not(:last-child), .newtab-customize-panel-item:not(:last-child),
.newtab-search-panel-engine:not(:last-child) { .newtab-search-panel-engine {
border-bottom: 1px solid #ccc; border-bottom: 1px solid threedshadow;
} }
.newtab-search-panel-engine > image { .newtab-search-panel-engine > image {
@ -485,10 +478,10 @@ input[type=button] {
} }
.newtab-customize-panel-item > label, .newtab-customize-panel-item > label,
.newtab-search-panel-engine > label { .newtab-search-panel-engine > label,
-moz-padding-start: 0; #newtab-search-manage > label {
-moz-margin-start: 0; padding: 0;
color: rgb(130, 132, 133); margin: 0;
} }
.newtab-customize-panel-item[selected], .newtab-customize-panel-item[selected],

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

@ -29,7 +29,7 @@
<xul:panel id="newtab-search-panel" orient="vertical" type="arrow" <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
noautohide="true" hidden="true"> noautohide="true" hidden="true">
<xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine"> <xul:hbox id="newtab-search-manage">
<xul:label>&changeSearchSettings.button;</xul:label> <xul:label>&changeSearchSettings.button;</xul:label>
</xul:hbox> </xul:hbox>
</xul:panel> </xul:panel>

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

@ -1659,6 +1659,9 @@
// allows the TabLabelModified event to be properly dispatched. // allows the TabLabelModified event to be properly dispatched.
if (!aURI || isBlankPageURL(aURI)) { if (!aURI || isBlankPageURL(aURI)) {
t.label = this.mStringBundle.getString("tabs.emptyTabTitle"); t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
} else if (aURI.toLowerCase().startsWith("javascript:")) {
// This can go away when bug 672618 or bug 55696 are fixed.
t.label = aURI;
} }
this.tabContainer.updateVisibility(); this.tabContainer.updateVisibility();

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

@ -121,7 +121,7 @@ function setUpTest(aTestName, aIDForNextTest, aFuncForNextTest, aChildTabLink) {
mainTab = gTestWin.gBrowser.selectedTab; mainTab = gTestWin.gBrowser.selectedTab;
// get the link for the next test from the main page // get the link for the next test from the main page
let target = gTestWin.content.document.getElementById(aIDForNextTest); let target = gTestWin.content.document.getElementById(aIDForNextTest).href;
gTestWin.gBrowser.addTab(target); gTestWin.gBrowser.addTab(target);
gTestWin.gBrowser.selectTabAtIndex(1); gTestWin.gBrowser.selectTabAtIndex(1);
waitForSomeTabToLoad(checkPopUpNotification); waitForSomeTabToLoad(checkPopUpNotification);

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

@ -130,7 +130,6 @@ skip-if = os == "linux"
skip-if = e10s # bug 1090635 skip-if = e10s # bug 1090635
[browser_981305_separator_insertion.js] [browser_981305_separator_insertion.js]
[browser_988072_sidebar_events.js] [browser_988072_sidebar_events.js]
skip-if = e10s # bug 1101482 - fails in --run-by-dir
[browser_989338_saved_placements_not_resaved.js] [browser_989338_saved_placements_not_resaved.js]
[browser_989751_subviewbutton_class.js] [browser_989751_subviewbutton_class.js]
[browser_987177_destroyWidget_xul.js] [browser_987177_destroyWidget_xul.js]

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

@ -80,8 +80,7 @@ let showSidebarPopup = Task.async(function*() {
let subviewShownPromise = subviewShown(subview); let subviewShownPromise = subviewShown(subview);
EventUtils.synthesizeMouseAtCenter(button, {}); EventUtils.synthesizeMouseAtCenter(button, {});
yield subviewShownPromise; return subviewShownPromise;
return waitForCondition(() => !subview.panelMultiView.hasAttribute("transitioning"));
}); });
// Check the sidebar widget shows the default items // Check the sidebar widget shows the default items

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

@ -537,6 +537,12 @@ loop.panel = (function(_, mozL10n) {
return {edit: false, text: this.props.text}; return {edit: false, text: this.props.text};
}, },
componentWillReceiveProps: function(nextProps) {
if (nextProps.text !== this.props.text) {
this.setState({text: nextProps.text});
}
},
handleTextClick: function(event) { handleTextClick: function(event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();

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

@ -537,6 +537,12 @@ loop.panel = (function(_, mozL10n) {
return {edit: false, text: this.props.text}; return {edit: false, text: this.props.text};
}, },
componentWillReceiveProps: function(nextProps) {
if (nextProps.text !== this.props.text) {
this.setState({text: nextProps.text});
}
},
handleTextClick: function(event) { handleTextClick: function(event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();

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

@ -391,8 +391,7 @@ loop.store.ActiveRoomStore = (function() {
this._leaveRoom(ROOM_STATES.CLOSING); this._leaveRoom(ROOM_STATES.CLOSING);
// If we're closing the window, we can stop listening to updates. // If we're closing the window, we can stop listening to updates.
this._mozLoop.rooms.off("update:" + this.getStoreState().roomToken, this._mozLoop.rooms.off("update:" + this.getStoreState().roomToken);
this._handleRoomUpdate.bind(this));
}, },
/** /**

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

@ -857,6 +857,25 @@ describe("loop.panel", function() {
new sharedActions.OpenRoom({roomToken: roomData.roomToken})); new sharedActions.OpenRoom({roomToken: roomData.roomToken}));
}); });
}); });
describe("Room name updated", function() {
it("should update room name", function() {
var roomEntry = mountRoomEntry({
dispatcher: dispatcher,
room: new loop.store.Room(roomData)
});
var updatedRoom = new loop.store.Room(_.extend({}, roomData, {
roomName: "New room name",
ctime: new Date().getTime()
}));
roomEntry.setProps({room: updatedRoom});
expect(
roomEntry.getDOMNode().querySelector(".edit-in-place").textContent)
.eql("New room name");
});
});
}); });
describe("loop.panel.RoomList", function() { describe("loop.panel.RoomList", function() {

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

@ -2,8 +2,8 @@
subsuite = devtools subsuite = devtools
support-files = support-files =
browser_fontinspector.html browser_fontinspector.html
ostrich-black.woff ostrich-black.ttf
ostrich-regular.woff ostrich-regular.ttf
head.js head.js
[browser_fontinspector.js] [browser_fontinspector.js]

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

@ -3,12 +3,12 @@
<style> <style>
@font-face { @font-face {
font-family: bar; font-family: bar;
src: url(bad/font/name.ttf), url(ostrich-regular.woff) format("woff"); src: url(bad/font/name.ttf), url(ostrich-regular.ttf) format("truetype");
} }
@font-face { @font-face {
font-family: bar; font-family: bar;
font-weight: 800; font-weight: 800;
src: url(ostrich-black.woff); src: url(ostrich-black.ttf);
} }
body{ body{

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

@ -47,10 +47,10 @@ function* testBodyFonts(inspector) {
ok(s[0].classList.contains("is-remote"), ok(s[0].classList.contains("is-remote"),
"font 0: is remote"); "font 0: is remote");
is(s[0].querySelector(".font-url").value, is(s[0].querySelector(".font-url").value,
"http://mochi.test:8888/browser/browser/devtools/fontinspector/test/ostrich-regular.woff", "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/ostrich-regular.ttf",
"font 0: right url"); "font 0: right url");
is(s[0].querySelector(".font-format").textContent, is(s[0].querySelector(".font-format").textContent,
"woff", "font 0: right font format"); "truetype", "font 0: right font format");
is(s[0].querySelector(".font-css-name").textContent, is(s[0].querySelector(".font-css-name").textContent,
"bar", "font 0: right css name"); "bar", "font 0: right css name");

Двоичные данные
browser/devtools/fontinspector/test/ostrich-black.ttf Executable file

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичные данные
browser/devtools/fontinspector/test/ostrich-regular.ttf Executable file

Двоичный файл не отображается.

Двоичный файл не отображается.

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

@ -93,6 +93,7 @@ browser.jar:
content/browser/devtools/performance/views/overview.js (performance/views/overview.js) content/browser/devtools/performance/views/overview.js (performance/views/overview.js)
content/browser/devtools/performance/views/details.js (performance/views/details.js) content/browser/devtools/performance/views/details.js (performance/views/details.js)
content/browser/devtools/performance/views/call-tree.js (performance/views/call-tree.js) content/browser/devtools/performance/views/call-tree.js (performance/views/call-tree.js)
content/browser/devtools/performance/views/waterfall.js (performance/views/waterfall.js)
#endif #endif
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js) content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
content/browser/devtools/commandline.css (commandline/commandline.css) content/browser/devtools/commandline.css (commandline/commandline.css)

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

@ -2317,7 +2317,7 @@ ElementEditor.prototype = {
* Called when the tag name editor has is done editing. * Called when the tag name editor has is done editing.
*/ */
onTagEdit: function(newTagName, isCommit) { onTagEdit: function(newTagName, isCommit) {
if (!isCommit || newTagName == this.node.tagName || if (!isCommit || newTagName.toLowerCase() === this.node.tagName.toLowerCase() ||
!("editTagName" in this.markup.walker)) { !("editTagName" in this.markup.walker)) {
return; return;
} }

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

@ -81,6 +81,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
[browser_markupview_tag_edit_08.js] [browser_markupview_tag_edit_08.js]
[browser_markupview_tag_edit_09.js] [browser_markupview_tag_edit_09.js]
[browser_markupview_tag_edit_10.js] [browser_markupview_tag_edit_10.js]
[browser_markupview_tag_edit_11.js]
[browser_markupview_textcontent_edit_01.js] [browser_markupview_textcontent_edit_01.js]
[browser_markupview_toggle_01.js] [browser_markupview_toggle_01.js]
[browser_markupview_toggle_02.js] [browser_markupview_toggle_02.js]

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

@ -0,0 +1,36 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Bug 1090874 - Tests that a node is not recreated when it's tagname editor
// is blurred and no changes were done.
const TEST_URL = "data:text/html;charset=utf-8,<div></div>";
let test = asyncTest(function*() {
let isEditTagNameCalled = false;
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
// Overriding the editTagName walkerActor method here to check that it isn't
// called when blurring the tagname field.
inspector.walker.editTagName = function() { isEditTagNameCalled = true; }
yield selectNode("div", inspector);
let container = yield getContainerForSelector("div", inspector);
let tagEditor = container.editor.tag;
info("Blurring the tagname field");
tagEditor.blur();
is(isEditTagNameCalled, false, "The editTagName method wasn't called");
info("Updating the tagname to uppercase");
setEditableFieldValue(tagEditor, "DIV", inspector);
is(isEditTagNameCalled, false, "The editTagName method wasn't called");
info("Updating the tagname to a different value");
setEditableFieldValue(tagEditor, "SPAN", inspector);
is(isEditTagNameCalled, true, "The editTagName method was called");
});

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

@ -23,6 +23,10 @@ devtools.lazyRequireGetter(this, "L10N",
"devtools/profiler/global", true); "devtools/profiler/global", true);
devtools.lazyImporter(this, "LineGraphWidget", devtools.lazyImporter(this, "LineGraphWidget",
"resource:///modules/devtools/Graphs.jsm"); "resource:///modules/devtools/Graphs.jsm");
devtools.lazyRequireGetter(this, "Waterfall",
"devtools/timeline/waterfall", true);
devtools.lazyRequireGetter(this, "MarkerDetails",
"devtools/timeline/marker-details", true);
devtools.lazyRequireGetter(this, "CallView", devtools.lazyRequireGetter(this, "CallView",
"devtools/profiler/tree-view", true); "devtools/profiler/tree-view", true);
devtools.lazyRequireGetter(this, "ThreadNode", devtools.lazyRequireGetter(this, "ThreadNode",
@ -47,8 +51,14 @@ const EVENTS = {
// Emitted by the OverviewView when a selection range has been removed // Emitted by the OverviewView when a selection range has been removed
OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared", OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared",
// Emitted by the DetailsView when a subview is selected
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
// Emitted by the CallTreeView when a call tree has been rendered // Emitted by the CallTreeView when a call tree has been rendered
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered" CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered",
// Emitted by the WaterfallView when it has been rendered
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered"
}; };
/** /**
@ -112,6 +122,7 @@ let PerformanceController = {
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording); PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording); PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
gFront.on("ticks", this._onTimelineData); gFront.on("ticks", this._onTimelineData);
gFront.on("markers", this._onTimelineData);
}, },
/** /**
@ -120,6 +131,8 @@ let PerformanceController = {
destroy: function() { destroy: function() {
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording); PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording); PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
gFront.off("ticks", this._onTimelineData);
gFront.off("markers", this._onTimelineData);
}, },
/** /**
@ -127,8 +140,12 @@ let PerformanceController = {
* when the front is starting to record. * when the front is starting to record.
*/ */
startRecording: Task.async(function *() { startRecording: Task.async(function *() {
yield gFront.startRecording(); // Save local start time for use with faking the endTime
this.emit(EVENTS.RECORDING_STARTED); // if not returned from the timeline actor
this._localStartTime = performance.now();
let startTime = this._startTime = yield gFront.startRecording();
this.emit(EVENTS.RECORDING_STARTED, startTime);
}), }),
/** /**
@ -137,6 +154,12 @@ let PerformanceController = {
*/ */
stopRecording: Task.async(function *() { stopRecording: Task.async(function *() {
let results = yield gFront.stopRecording(); let results = yield gFront.stopRecording();
// If `endTime` is not yielded from timeline actor (< Fx36),
// fake an endTime
if (!results.endTime) {
this._endTime = results.endTime = this._startTime + (performance.now() - this._localStartTime);
}
this.emit(EVENTS.RECORDING_STOPPED, results); this.emit(EVENTS.RECORDING_STOPPED, results);
}), }),

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

@ -237,7 +237,12 @@ PerformanceFront.prototype = {
// The timeline actor is target-dependent, so just make sure // The timeline actor is target-dependent, so just make sure
// it's recording. // it's recording.
let withMemory = showTimelineMemory(); let withMemory = showTimelineMemory();
yield this._request("timeline", "start", { withTicks: true, withMemory: withMemory });
// Return start time from timeline actor
let startTime = yield this._request("timeline", "start", { withTicks: true, withMemory: withMemory });
this._startTime = startTime;
return { startTime };
}), }),
/** /**
@ -254,12 +259,13 @@ PerformanceFront.prototype = {
filterSamples(profilerData, this._profilingStartTime); filterSamples(profilerData, this._profilingStartTime);
offsetSampleTimes(profilerData, this._profilingStartTime); offsetSampleTimes(profilerData, this._profilingStartTime);
yield this._request("timeline", "stop"); let endTime = this._endTime = yield this._request("timeline", "stop");
// Join all the acquired data and return it for outside consumers. // Join all the acquired data and return it for outside consumers.
return { return {
recordingDuration: profilerData.currentTime - this._profilingStartTime, recordingDuration: profilerData.currentTime - this._profilingStartTime,
profilerData: profilerData profilerData: profilerData,
endTime: endTime
}; };
}), }),

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

@ -19,6 +19,7 @@
<script type="application/javascript" src="performance/views/overview.js"/> <script type="application/javascript" src="performance/views/overview.js"/>
<script type="application/javascript" src="performance/views/details.js"/> <script type="application/javascript" src="performance/views/details.js"/>
<script type="application/javascript" src="performance/views/call-tree.js"/> <script type="application/javascript" src="performance/views/call-tree.js"/>
<script type="application/javascript" src="performance/views/waterfall.js"/>
<vbox class="theme-body" flex="1"> <vbox class="theme-body" flex="1">
<toolbar id="performance-toolbar" class="devtools-toolbar"> <toolbar id="performance-toolbar" class="devtools-toolbar">
@ -37,17 +38,32 @@
label="&profilerUI.importButton;"/> label="&profilerUI.importButton;"/>
</hbox> </hbox>
</toolbar> </toolbar>
<splitter class="devtools-horizontal-splitter" />
<box id="overview-pane" <box id="overview-pane"
class="devtools-responsive-container" class="devtools-responsive-container">
flex="1">
<vbox id="time-framerate" flex="1"/> <vbox id="time-framerate" flex="1"/>
</box> </box>
<splitter class="devtools-horizontal-splitter" /> <toolbar id="details-toolbar" class="devtools-toolbar">
<box id="details-pane" <hbox class="devtools-toolbarbutton-group">
<toolbarbutton id="select-waterfall-view"
class="devtools-toolbarbutton"
tooltiptext="waterfall"
data-view="waterfall" />
<toolbarbutton id="select-calltree-view"
class="devtools-toolbarbutton"
tooltiptext="calltree"
data-view="calltree" />
</hbox>
</toolbar>
<deck id="details-pane"
class="devtools-responsive-container" class="devtools-responsive-container"
flex="1"> flex="1">
<vbox class="call-tree" flex="1"> <hbox id="waterfall-view" flex="1">
<vbox id="waterfall-graph" flex="1" />
<splitter class="devtools-side-splitter"/>
<vbox id="waterfall-details" class="theme-sidebar" width="150" height="150"/>
</hbox>
<vbox id="calltree-view" class="call-tree" flex="1">
<hbox class="call-tree-headers-container"> <hbox class="call-tree-headers-container">
<label class="plain call-tree-header" <label class="plain call-tree-header"
type="duration" type="duration"
@ -76,6 +92,6 @@
</hbox> </hbox>
<vbox class="call-tree-cells-container" flex="1"/> <vbox class="call-tree-cells-container" flex="1"/>
</vbox> </vbox>
</box> </deck>
</vbox> </vbox>
</window> </window>

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

@ -9,6 +9,7 @@ support-files =
# that need to be moved over to performance tool # that need to be moved over to performance tool
[browser_perf-aaa-run-first-leaktest.js] [browser_perf-aaa-run-first-leaktest.js]
[browser_perf-front.js]
[browser_perf-front-basic-timeline-01.js] [browser_perf-front-basic-timeline-01.js]
[browser_perf-front-basic-profiler-01.js] [browser_perf-front-basic-profiler-01.js]
# bug 1077464 # bug 1077464
@ -32,5 +33,8 @@ support-files =
[browser_perf-overview-render-01.js] [browser_perf-overview-render-01.js]
[browser_perf-overview-render-02.js] [browser_perf-overview-render-02.js]
[browser_perf-overview-selection.js] [browser_perf-overview-selection.js]
[browser_perf-details.js]
[browser_perf-details-calltree-render-01.js] [browser_perf-details-calltree-render-01.js]
[browser_perf-details-calltree-render-02.js] [browser_perf-details-calltree-render-02.js]
[browser_perf-details-waterfall-render-01.js]

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

@ -0,0 +1,26 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the waterfall view renders content after recording.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, WaterfallView } = panel.panelWin;
yield startRecording(panel);
yield waitUntil(() => WaterfallView._markers.length);
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "WaterfallView rendered after recording is stopped.");
ok(WaterfallView._markers.length, "WaterfallView contains markers");
yield teardown(panel);
finish();
}

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

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the details view toggles subviews.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, $, DetailsView, document: doc } = panel.panelWin;
info("views on startup");
checkViews(DetailsView, doc, "waterfall");
// Select calltree view
let viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
command($("toolbarbutton[data-view='calltree']"));
let [_, viewName] = yield viewChanged;
is(viewName, "calltree", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "calltree");
// Select waterfall view
viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
command($("toolbarbutton[data-view='waterfall']"));
[_, viewName] = yield viewChanged;
is(viewName, "waterfall", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "waterfall");
yield teardown(panel);
finish();
}
function checkViews (DetailsView, doc, currentView) {
for (let viewName in DetailsView.views) {
let view = DetailsView.views[viewName].el;
let button = doc.querySelector("toolbarbutton[data-view='" + viewName + "']");
if (viewName === currentView) {
ok(!view.getAttribute("hidden"), view + " view displayed");
ok(button.getAttribute("checked"), view + " button checked");
} else {
ok(view.getAttribute("hidden"), view + " view hidden");
ok(!button.getAttribute("checked"), view + " button not checked");
}
}
}

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

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test basic functionality of PerformanceFront, emitting start and endtime values
*/
let WAIT = 1000;
function spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let { startTime } = yield front.startRecording();
ok(typeof startTime === "number", "front.startRecording() emits start time");
yield busyWait(WAIT);
let { endTime } = yield front.stopRecording();
ok(typeof endTime === "number", "front.stopRecording() emits end time");
ok(endTime > startTime, "endTime is after startTime");
yield removeTab(target.tab);
finish();
}

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

@ -211,8 +211,14 @@ function busyWait(time) {
while (Date.now() - start < time) { stack = Components.stack; } while (Date.now() - start < time) { stack = Components.stack; }
} }
function idleWait(time) { function command (button) {
return DevToolsUtils.waitForTime(time); let ev = button.ownerDocument.createEvent("XULCommandEvent");
ev.initCommandEvent("command", true, true, button.ownerDocument.defaultView, 0, false, false, false, false, null);
button.dispatchEvent(ev);
}
function click (win, button) {
EventUtils.sendMouseEvent({ type: "click" }, button, win);
} }
function* startRecording(panel) { function* startRecording(panel) {
@ -227,7 +233,7 @@ function* startRecording(panel) {
ok(!button.hasAttribute("locked"), ok(!button.hasAttribute("locked"),
"The record button should not be locked yet."); "The record button should not be locked yet.");
EventUtils.sendMouseEvent({ type: "click" }, button, win); click(win, button);
yield clicked; yield clicked;
@ -255,7 +261,7 @@ function* stopRecording(panel) {
ok(!button.hasAttribute("locked"), ok(!button.hasAttribute("locked"),
"The record button should not be locked yet."); "The record button should not be locked yet.");
EventUtils.sendMouseEvent({ type: "click" }, button, win); click(win, button);
yield clicked; yield clicked;

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

@ -3,34 +3,69 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"; "use strict";
const DEFAULT_DETAILS_SUBVIEW = "waterfall";
/** /**
* Details view containing profiler call tree. Manages * Details view containing profiler call tree. Manages
* subviews and toggles visibility between them. * subviews and toggles visibility between them.
*/ */
let DetailsView = { let DetailsView = {
/**
* Name to index mapping of subviews, used by selecting view.
*/
viewIndexes: {
waterfall: 0,
calltree: 1
},
/** /**
* Sets up the view with event binding, initializes * Sets up the view with event binding, initializes
* subviews. * subviews.
*/ */
initialize: function () { initialize: Task.async(function *() {
this.views = { this.el = $("#details-pane");
callTree: CallTreeView
};
// Initialize subviews this._onViewToggle = this._onViewToggle.bind(this);
return promise.all([
CallTreeView.initialize() for (let button of $$("toolbarbutton[data-view]", $("#details-toolbar"))) {
]); button.addEventListener("command", this._onViewToggle);
}
yield CallTreeView.initialize();
yield WaterfallView.initialize();
this.selectView(DEFAULT_DETAILS_SUBVIEW);
}),
/**
* Select one of the DetailView's subviews to be rendered,
* hiding the others.
*
* @params {String} selectedView
* Name of the view to be shown.
*/
selectView: function (selectedView) {
this.el.selectedIndex = this.viewIndexes[selectedView];
this.emit(EVENTS.DETAILS_VIEW_SELECTED, selectedView);
},
/**
* Called when a view button is clicked.
*/
_onViewToggle: function (e) {
this.selectView(e.target.getAttribute("data-view"));
}, },
/** /**
* Unbinds events, destroys subviews. * Unbinds events, destroys subviews.
*/ */
destroy: function () { destroy: Task.async(function *() {
return promise.all([ for (let button of $$("toolbarbutton[data-view]", $("#details-toolbar"))) {
CallTreeView.destroy() button.removeEventListener("command", this._onViewToggle);
]); }
}
yield CallTreeView.destroy();
yield WaterfallView.destroy();
})
}; };
/** /**

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

@ -0,0 +1,116 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Waterfall view controlled by DetailsView.
*/
let WaterfallView = {
_startTime: 0,
_endTime: 0,
_markers: [],
/**
* Sets up the view with event binding.
*/
initialize: Task.async(function *() {
this.el = $("#waterfall-view");
this._stop = this._stop.bind(this);
this._start = this._start.bind(this);
this._onTimelineData = this._onTimelineData.bind(this);
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this._onResize = this._onResize.bind(this);
this.graph = new Waterfall($("#waterfall-graph"), $("#details-pane"));
this.markerDetails = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
this.graph.on("selected", this._onMarkerSelected);
this.graph.on("unselected", this._onMarkerSelected);
this.markerDetails.on("resize", this._onResize);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._start);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
PerformanceController.on(EVENTS.TIMELINE_DATA, this._onTimelineData);
yield this.graph.recalculateBounds();
}),
/**
* Unbinds events.
*/
destroy: function () {
this.graph.off("selected", this._onMarkerSelected);
this.graph.off("unselected", this._onMarkerSelected);
this.markerDetails.off("resize", this._onResize);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._start);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
PerformanceController.off(EVENTS.TIMELINE_DATA, this._onTimelineData);
},
render: Task.async(function *() {
yield this.graph.recalculateBounds();
this.graph.setData(this._markers, this._startTime, this._startTime, this._endTime);
this.emit(EVENTS.WATERFALL_RENDERED);
}),
/**
* Event handlers
*/
/**
* Called when recording starts.
*/
_start: function (_, { startTime }) {
this._startTime = startTime;
this._endTime = startTime;
this.graph.clearView();
},
/**
* Called when recording stops.
*/
_stop: Task.async(function *(_, { endTime }) {
this._endTime = endTime;
this._markers = this._markers.sort((a,b) => (a.start > b.start));
this.render();
}),
/**
* Called when a marker is selected in the waterfall view,
* updating the markers detail view.
*/
_onMarkerSelected: function (event, marker) {
if (event === "selected") {
this.markerDetails.render(marker);
}
if (event === "unselected") {
this.markerDetails.empty();
}
},
/**
* Called when the marker details view is resized.
*/
_onResize: function () {
this.render();
},
/**
* Called when the TimelineFront has new data for
* framerate, markers or memory, and stores the data
* to be plotted subsequently.
*/
_onTimelineData: function (_, eventName, ...data) {
if (eventName === "markers") {
let [markers, endTime] = data;
Array.prototype.push.apply(this._markers, markers);
}
}
};
/**
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(WaterfallView);

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

@ -251,8 +251,8 @@ let TimelineView = {
*/ */
initialize: Task.async(function*() { initialize: Task.async(function*() {
this.markersOverview = new MarkersOverview($("#markers-overview")); this.markersOverview = new MarkersOverview($("#markers-overview"));
this.waterfall = new Waterfall($("#timeline-waterfall")); this.waterfall = new Waterfall($("#timeline-waterfall"), $("#timeline-pane"));
this.markerDetails = new MarkerDetails($("#timeline-waterfall-details")); this.markerDetails = new MarkerDetails($("#timeline-waterfall-details"), $("#timeline-waterfall-container > splitter"));
this._onSelecting = this._onSelecting.bind(this); this._onSelecting = this._onSelecting.bind(this);
this._onRefresh = this._onRefresh.bind(this); this._onRefresh = this._onRefresh.bind(this);
@ -273,8 +273,10 @@ let TimelineView = {
*/ */
destroy: function() { destroy: function() {
this.markerDetails.off("resize", this._onRefresh); this.markerDetails.off("resize", this._onRefresh);
this.markerDetails.destroy();
this.waterfall.off("selected", this._onMarkerSelected); this.waterfall.off("selected", this._onMarkerSelected);
this.waterfall.off("unselected", this._onMarkerSelected); this.waterfall.off("unselected", this._onMarkerSelected);
this.waterfall.destroy();
this.markersOverview.off("selecting", this._onSelecting); this.markersOverview.off("selecting", this._onSelecting);
this.markersOverview.off("refresh", this._onRefresh); this.markersOverview.off("refresh", this._onRefresh);
this.markersOverview.destroy(); this.markersOverview.destroy();

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

@ -22,12 +22,14 @@ loader.lazyRequireGetter(this, "EventEmitter",
* *
* @param nsIDOMNode parent * @param nsIDOMNode parent
* The parent node holding the view. * The parent node holding the view.
* @param nsIDOMNode splitter
* The splitter node that the resize event is bound to.
*/ */
function MarkerDetails(parent) { function MarkerDetails(parent, splitter) {
EventEmitter.decorate(this); EventEmitter.decorate(this);
this._document = parent.ownerDocument; this._document = parent.ownerDocument;
this._parent = parent; this._parent = parent;
this._splitter = this._document.querySelector("#timeline-waterfall-container > splitter"); this._splitter = splitter;
this._splitter.addEventListener("mouseup", () => this.emit("resize")); this._splitter.addEventListener("mouseup", () => this.emit("resize"));
} }
@ -35,6 +37,7 @@ MarkerDetails.prototype = {
destroy: function() { destroy: function() {
this.empty(); this.empty();
this._parent = null; this._parent = null;
this._splitter = null;
}, },
/** /**

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

@ -48,11 +48,14 @@ const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
* *
* @param nsIDOMNode parent * @param nsIDOMNode parent
* The parent node holding the waterfall. * The parent node holding the waterfall.
* @param nsIDOMNode container
* The container node that key events should be bound to.
*/ */
function Waterfall(parent) { function Waterfall(parent, container) {
EventEmitter.decorate(this); EventEmitter.decorate(this);
this._parent = parent; this._parent = parent;
this._document = parent.ownerDocument; this._document = parent.ownerDocument;
this._container = container;
this._fragment = this._document.createDocumentFragment(); this._fragment = this._document.createDocumentFragment();
this._outstandingMarkers = []; this._outstandingMarkers = [];
@ -78,9 +81,15 @@ function Waterfall(parent) {
// Selected row index. By default, we want the first // Selected row index. By default, we want the first
// row to be selected. // row to be selected.
this._selectedRowIdx = 0; this._selectedRowIdx = 0;
// Default rowCount
this.rowCount = WATERFALL_ROWCOUNT_ONPAGEUPDOWN;
} }
Waterfall.prototype = { Waterfall.prototype = {
destroy: function() {
this._parent = this._document = this._container = null;
},
/** /**
* Populates this view with the provided data source. * Populates this view with the provided data source.
* *
@ -110,7 +119,7 @@ Waterfall.prototype = {
* Keybindings. * Keybindings.
*/ */
setupKeys: function() { setupKeys: function() {
let pane = this._document.querySelector("#timeline-pane"); let pane = this._container;
pane.parentNode.parentNode.addEventListener("keydown", e => { pane.parentNode.parentNode.addEventListener("keydown", e => {
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) { if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
e.preventDefault(); e.preventDefault();
@ -130,11 +139,11 @@ Waterfall.prototype = {
} }
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) { if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
e.preventDefault(); e.preventDefault();
this.selectNearestRow(this._selectedRowIdx - WATERFALL_ROWCOUNT_ONPAGEUPDOWN); this.selectNearestRow(this._selectedRowIdx - this.rowCount);
} }
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) { if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
e.preventDefault(); e.preventDefault();
this.selectNearestRow(this._selectedRowIdx + WATERFALL_ROWCOUNT_ONPAGEUPDOWN); this.selectNearestRow(this._selectedRowIdx + this.rowCount);
} }
}, true); }, true);
}, },

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

@ -204,3 +204,12 @@ functionality specific to firefox. -->
<!ENTITY remoteXUL.title "Remote XUL"> <!ENTITY remoteXUL.title "Remote XUL">
<!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>"> <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
<!ENTITY oldSecurityProtocol.title "Unable to Connect Securely">
<!-- LOCALIZATION NOTE (oldSecurityProtocol.longDesc) - Do not translate the
<span id='hostname'></span>. -->
<!ENTITY oldSecurityProtocol.longDesc "Firefox cannot guarantee the safety of your data on <span id='hostname'></span> because it uses SSLv3, a broken security protocol.">
<!-- LOCALIZATION NOTE (oldSecurityProtocol.advancedInfo) - Do not translate
the <span id='errorcode'></span>. -->
<!ENTITY oldSecurityProtocol.advancedInfo "Advanced info: <span id='errorcode'></span>">
<!ENTITY oldSecurityProtocol.learnMore "Learn More…">

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

@ -10,11 +10,16 @@
background-color: white; background-color: white;
border-color: hsla(210,100%,85%,0.7); border-color: hsla(210,100%,85%,0.7);
border-style: dotted; border-style: dotted;
color: hsl(210,53%,45%); color: var(--theme-selection-color);
}
.margin,
.size {
color: var(--theme-highlight-blue);
} }
#content { #content {
background-color: #80d4ff; background-color: #87ceeb;
border-color: hsl(210,100%,85%); border-color: hsl(210,100%,85%);
border-style: dotted; border-style: dotted;
} }
@ -26,18 +31,19 @@
} }
#padding { #padding {
background-color: #66cc52; background-color: #6a5acd;
} }
#borders { #borders {
background-color: #ffe431; background-color: #444444;
border-style: dotted; border-style: dotted;
border-color: hsl(210,100%,85%); border-color: hsl(210,100%,85%);
} }
#margins { #margins {
background-color: #d89b28; background-color: #edff64;
opacity: 0.6; /* This opacity applies to all of the regions, since they are nested. */
opacity: .8;
} }
.editable { .editable {

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

@ -33,6 +33,17 @@
pointer-events: none; pointer-events: none;
} }
/* Details Panel */
#select-waterfall-view {
list-style-image: url(performance-icons.svg#waterfall);
}
#select-calltree-view {
list-style-image: url(performance-icons.svg#call-tree);
}
/* Profile call tree */ /* Profile call tree */
.theme-dark .call-tree-headers-container { .theme-dark .call-tree-headers-container {
@ -255,3 +266,148 @@
transform: scale(0.75); transform: scale(0.75);
transform-origin: center right; transform-origin: center right;
} }
/**
* Details Waterfall Styles
*/
.waterfall-list-contents {
/* Hack: force hardware acceleration */
transform: translateZ(1px);
overflow-x: hidden;
overflow-y: auto;
}
.waterfall-header-contents {
overflow-x: hidden;
}
.waterfall-background-ticks {
/* Background created on a <canvas> in js. */
/* @see browser/devtools/timeline/widgets/waterfall.js */
background-image: -moz-element(#waterfall-background);
background-repeat: repeat-y;
background-position: -1px center;
}
.waterfall-marker-container[is-spacer] {
pointer-events: none;
}
.theme-dark .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
background-color: rgba(255,255,255,0.03);
}
.theme-light .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
background-color: rgba(128,128,128,0.03);
}
.theme-dark .waterfall-marker-container:hover {
background-color: rgba(255,255,255,0.1) !important;
}
.theme-light .waterfall-marker-container:hover {
background-color: rgba(128,128,128,0.1) !important;
}
.waterfall-marker-item {
overflow: hidden;
}
.waterfall-sidebar {
-moz-border-end: 1px solid;
}
.theme-dark .waterfall-sidebar {
-moz-border-end-color: #000;
}
.theme-light .waterfall-sidebar {
-moz-border-end-color: #aaa;
}
.waterfall-marker-container:hover > .waterfall-sidebar {
background-color: transparent;
}
.waterfall-header-name {
padding: 4px;
}
.waterfall-header-tick {
width: 100px;
font-size: 9px;
transform-origin: left center;
}
.theme-dark .waterfall-header-tick {
color: #a9bacb;
}
.theme-light .waterfall-header-tick {
color: #292e33;
}
.waterfall-header-tick:not(:first-child) {
-moz-margin-start: -100px !important; /* Don't affect layout. */
}
.waterfall-marker-bullet {
width: 8px;
height: 8px;
-moz-margin-start: 8px;
-moz-margin-end: 6px;
border: 1px solid;
border-radius: 1px;
}
.waterfall-marker-name {
font-size: 95%;
padding-bottom: 1px !important;
}
.waterfall-marker-bar {
height: 9px;
border: 1px solid;
border-radius: 1px;
transform-origin: left center;
}
.theme-light .waterfall-marker-container.selected > .waterfall-sidebar,
.theme-light .waterfall-marker-container.selected > .waterfall-marker-item {
background-color: #4c9ed9; /* Select Highlight Blue */
color: #f5f7fa; /* Light foreground text */
}
.theme-dark .waterfall-marker-container.selected > .waterfall-sidebar,
.theme-dark .waterfall-marker-container.selected > .waterfall-marker-item {
background-color: #1d4f73; /* Select Highlight Blue */
color: #f5f7fa; /* Light foreground text */
}
.waterfall-marker-container.selected .waterfall-marker-bullet,
.waterfall-marker-container.selected .waterfall-marker-bar {
border-color: initial!important;
}
#waterfall-details {
padding-top: 28px;
overflow: auto;
}
.marker-details-bullet {
width: 8px;
height: 8px;
margin: 0 8px;
border: 1px solid;
border-radius: 1px;
}
.marker-details-type {
font-size: 1.2em;
font-weight: bold;
}
.marker-details-duration {
font-weight: bold;
}

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

@ -7,6 +7,7 @@ package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.home.HomePanelsManager; import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.mozglue.GeckoLoader; import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.HardwareUtils;

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

@ -3,13 +3,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko; package org.mozilla.gecko.lwt;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.json.JSONObject; import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoSharedPrefs; import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.gecko.util.GeckoEventListener;

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

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko; package org.mozilla.gecko.lwt;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;

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

@ -326,10 +326,10 @@ gbjar.sources += [
'InputMethods.java', 'InputMethods.java',
'IntentHelper.java', 'IntentHelper.java',
'JavaAddonManager.java', 'JavaAddonManager.java',
'LightweightTheme.java',
'LightweightThemeDrawable.java',
'LocaleAware.java', 'LocaleAware.java',
'LocaleManager.java', 'LocaleManager.java',
'lwt/LightweightTheme.java',
'lwt/LightweightThemeDrawable.java',
'MediaCastingBar.java', 'MediaCastingBar.java',
'MemoryMonitor.java', 'MemoryMonitor.java',
'menu/GeckoMenu.java', 'menu/GeckoMenu.java',

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

@ -9,14 +9,14 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.LightweightThemeDrawable;
import org.mozilla.gecko.NewTabletUI; import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu; import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.mozilla.gecko.widget.IconTabWidget; import org.mozilla.gecko.widget.IconTabWidget;

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

@ -76,6 +76,16 @@ public class ToolbarComponent extends BaseComponent {
return this; return this;
} }
public ToolbarComponent assertIsUrlEditTextSelected() {
fAssertTrue("The edit text is selected", isUrlEditTextSelected());
return this;
}
public ToolbarComponent assertIsUrlEditTextNotSelected() {
fAssertFalse("The edit text is not selected", isUrlEditTextSelected());
return this;
}
/** /**
* Returns the root View for the browser toolbar. * Returns the root View for the browser toolbar.
*/ */
@ -192,7 +202,7 @@ public class ToolbarComponent extends BaseComponent {
urlEditText.isInputMethodTarget()); urlEditText.isInputMethodTarget());
mSolo.clearEditText(urlEditText); mSolo.clearEditText(urlEditText);
mSolo.enterText(urlEditText, url); mSolo.typeText(urlEditText, url);
return this; return this;
} }
@ -246,4 +256,8 @@ public class ToolbarComponent extends BaseComponent {
} }
}); });
} }
private boolean isUrlEditTextSelected() {
return getUrlEditText().isSelected();
}
} }

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

@ -134,6 +134,7 @@ skip-if = android_version == "10"
# disabled on Android 2.3; bug 946656 # disabled on Android 2.3; bug 946656
skip-if = android_version == "10" skip-if = android_version == "10"
[testAppMenuPathways] [testAppMenuPathways]
[testBackButtonInEditMode]
[testEventDispatcher] [testEventDispatcher]
[testGeckoRequest] [testGeckoRequest]
[testInputConnection] [testInputConnection]

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

@ -0,0 +1,47 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.tests.helpers.GeckoHelper;
import android.view.View;
/**
* Tests that verify the behavior of back button in edit mode.
*/
public class testBackButtonInEditMode extends UITest {
public void testBackButtonInEditMode() {
GeckoHelper.blockForReady();
// Verify back button behavior for edit mode.
mToolbar.enterEditingMode()
.assertIsUrlEditTextSelected();
testBackPressInEditMode();
testExitUsingBackButton();
// Verify back button behavior in edit mode after input.
mToolbar.enterEditingMode()
.enterUrl("dummy")
.assertIsUrlEditTextSelected();
testBackPressInEditMode();
testExitUsingBackButton();
// Verify the swipe behavior in edit mode.
mToolbar.enterEditingMode()
.assertIsUrlEditTextSelected();
mAboutHome.swipeToPanelOnLeft();
mToolbar.assertIsUrlEditTextNotSelected()
.assertIsEditing();
testExitUsingBackButton();
}
public void testBackPressInEditMode() {
// Press back button and verify URLEditText is not selected.
getSolo().goBack();
mToolbar.assertIsUrlEditTextNotSelected()
.assertIsEditing();
}
public void testExitUsingBackButton() {
getSolo().goBack();
mToolbar.assertIsNotEditing();
}
}

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

@ -14,7 +14,6 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserApp; import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.NewTabletUI; import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab; import org.mozilla.gecko.Tab;
@ -24,6 +23,7 @@ import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener; import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.MenuPopup; import org.mozilla.gecko.menu.MenuPopup;
import org.mozilla.gecko.tabs.TabHistoryController; import org.mozilla.gecko.tabs.TabHistoryController;

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

@ -5,9 +5,9 @@
package org.mozilla.gecko.toolbar; package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.LightweightThemeDrawable;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
import org.mozilla.gecko.tabs.TabCurve; import org.mozilla.gecko.tabs.TabCurve;
import org.mozilla.gecko.widget.ThemedImageButton; import org.mozilla.gecko.widget.ThemedImageButton;

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

@ -14,6 +14,10 @@ import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape; import android.graphics.drawable.shapes.Shape;
public class ResizablePathDrawable extends ShapeDrawable { public class ResizablePathDrawable extends ShapeDrawable {
// An attribute mirroring the super class' value. getAlpha() is only
// available in API 19+ so to use that alpha value, we have to mirror it.
private int alpha = 255;
private final ColorStateList colorStateList; private final ColorStateList colorStateList;
private int currentColor; private int currentColor;
@ -51,9 +55,21 @@ public class ResizablePathDrawable extends ShapeDrawable {
@Override @Override
protected void onDraw(Shape shape, Canvas canvas, Paint paint) { protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
paint.setColor(currentColor); paint.setColor(currentColor);
// setAlpha overrides the alpha value in set color. Since we just set the color,
// the alpha value is reset: override the alpha value with the old value.
//
// Note: We *should* be able to call Shape.setAlpha, rather than Paint.setAlpha, but
// then the opacity doesn't change - dunno why but probably not worth the time.
paint.setAlpha(alpha);
super.onDraw(shape, canvas, paint); super.onDraw(shape, canvas, paint);
} }
@Override
public void setAlpha(final int alpha) {
super.setAlpha(alpha);
this.alpha = alpha;
}
@Override @Override
protected boolean onStateChange(int[] stateSet) { protected boolean onStateChange(int[] stateSet) {
return updateColor(stateSet); return updateColor(stateSet);

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget; package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;

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

@ -27,23 +27,23 @@
/* Box model highlighter */ /* Box model highlighter */
:-moz-native-anonymous .box-model-regions { :-moz-native-anonymous .box-model-regions {
opacity: 0.4; opacity: 0.6;
} }
:-moz-native-anonymous .box-model-content { :-moz-native-anonymous .box-model-content {
fill: #80d4ff; fill: #87ceeb;
} }
:-moz-native-anonymous .box-model-padding { :-moz-native-anonymous .box-model-padding {
fill: #66cc52; fill: #6a5acd;
} }
:-moz-native-anonymous .box-model-border { :-moz-native-anonymous .box-model-border {
fill: #ffe431; fill: #444444;
} }
:-moz-native-anonymous .box-model-margin { :-moz-native-anonymous .box-model-margin {
fill: #d89b28; fill: #edff64;
} }
:-moz-native-anonymous .box-model-content, :-moz-native-anonymous .box-model-content,

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

@ -241,7 +241,14 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
} }
clearTimeout(this._dataPullTimeout); clearTimeout(this._dataPullTimeout);
}, {}), return this.docShells[0].now();
}, {
response: {
// Set as possibly nullable due to the end time possibly being
// undefined during destruction
value: RetVal("nullable:number")
}
}),
/** /**
* When a new window becomes available in the tabActor, start recording its * When a new window becomes available in the tabActor, start recording its

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

@ -21,6 +21,12 @@ let chromeGlobal = this;
if (!DebuggerServer.initialized) { if (!DebuggerServer.initialized) {
DebuggerServer.init(); DebuggerServer.init();
// message manager helpers provided for actor module parent/child message exchange
DebuggerServer.parentMessageManager = {
sendSyncMessage: sendSyncMessage,
addMessageListener: addMessageListener
};
} }
// In case of apps being loaded in parent process, DebuggerServer is already // In case of apps being loaded in parent process, DebuggerServer is already

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

@ -0,0 +1,116 @@
Lazy Actor Modules and E10S setup
---------------------------------
The **DebuggerServer** loads and creates most of the actors lazily to keep
the initial memory usage down (which is extremely important on lower end devices).
## Register a lazy global/tab actor module
register a global actor:
```js
DebuggerServer.registerModule("devtools/server/actors/webapps", {
prefix: "webapps",
constructor: "WebappsActor",
type: { global: true }
});
```
register a tab actor:
```js
DebuggerServer.registerModule("devtools/server/actors/webconsole", {
prefix: "console",
constructor: "WebConsoleActor",
type: { tab: true }
});
```
## E10S Setup
Some of the actor modules needs to exchange messages between the parent and child processes.
E.g. the **director-manager** needs to ask the list installed **director scripts** from
the **director-registry** running in the parent process) and the parent/child setup
is lazily directed by the **DebuggerServer**.
When the actor is loaded for the first time in the the **DebuggerServer** running in the
child process, it has the chances to run its setup procedure, e.g. in the **director-registry**:
```js
...
const {DebuggerServer} = require("devtools/server/main");
...
// skip child setup if this actor module is not running in a child process
if (DebuggerServer.isInChildProcess) {
setupChildProcess();
}
...
```
The above setupChildProcess helper will use the **DebuggerServer.setupInParent**
to start a setup process in the parent process Debugger Server, e.g. in the the **director-registry**:
```js
function setupChildProcess() {
const { sendSyncMessage } = DebuggerServer.parentMessageManager;
DebuggerServer.setupInParent({
module: "devtools/server/actors/director-registry",
setupParent: "setupParentProcess"
});
...
```
in the parent process, the **DebuggerServer** will require the requested module
and call the **setupParent** exported helper with the **MessageManager**
connected to the child process as parameter, e.g. in the **director-registry**:
```js
/**
* E10S parent/child setup helpers
*/
let gTrackedMessageManager = new Set();
exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
if (gTrackedMessageManager.has(mm)) { return; }
gTrackedMessageManager.add(mm);
// listen for director-script requests from the child process
mm.addMessageListener("debug:director-registry-request", handleChildRequest);
// time to unsubscribe from the disconnected message manager
DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
...
}
```
The DebuggerServer emits "disconnected-from-child:CHILDID" events to give the actor modules
the chance to cleanup their handlers registered on the disconnected message manager.
## E10S setup flow
In the child process:
- DebuggerServer loads an actor module
- the actor module check DebuggerServer.isInChildProcess
- the actor module calls the DebuggerServer.setupInParent helper
- the DebuggerServer.setupInParent helper asks to the parent process
to run the required setup
- the actor module use the DebuggerServer.parentMessageManager.sendSyncMessage,
DebuggerServer.parentMessageManager.addMessageListener helpers to send requests
or to subscribe message handlers
In the parent process:
- The DebuggerServer receives the DebuggerServer.setupInParent request
- it tries to load the required module
- it tries to call the **mod[setupParent]** method with the frame message manager and the childID
in the json parameter **{ mm, childID }**
- the module setupParent helper use the mm to subscribe the messagemanager events
- the module setupParent helper use the DebuggerServer object to subscribe *once* the
**"disconnected-from-child:CHILDID"** event (needed to unsubscribe the messagemanager events)

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

@ -137,7 +137,7 @@ function ModuleAPI() {
} }
activeGlobalActors = null; activeGlobalActors = null;
} }
} };
}; };
/*** /***
@ -213,7 +213,7 @@ var DebuggerServer = {
for (let id of Object.getOwnPropertyNames(gRegisteredModules)) { for (let id of Object.getOwnPropertyNames(gRegisteredModules)) {
this.unregisterModule(id); this.unregisterModule(id);
} }
gRegisteredModules = {}; gRegisteredModules = Object.create(null);
this.closeAllListeners(); this.closeAllListeners();
this.globalActorFactories = {}; this.globalActorFactories = {};
@ -711,6 +711,39 @@ var DebuggerServer = {
return deferred.promise; return deferred.promise;
}, },
/**
* Check if the caller is running in a content child process.
*
* @return boolean
* true if the caller is running in a content
*/
get isInChildProcess() !!this.parentMessageManager,
/**
* In a content child process, ask the DebuggerServer in the parent process
* to execute a given module setup helper.
*
* @param module
* The module to be required
* @param setupParent
* The name of the setup helper exported by the above module
* (setup helper signature: function ({mm}) { ... })
* @return boolean
* true if the setup helper returned successfully
*/
setupInParent: function({ module, setupParent }) {
if (!this.isInChildProcess) {
return false;
}
let { sendSyncMessage } = DebuggerServer.parentMessageManager;
return sendSyncMessage("debug:setup-in-parent", {
module: module,
setupParent: setupParent
});
},
/** /**
* Connect to a child process. * Connect to a child process.
* *
@ -736,6 +769,33 @@ var DebuggerServer = {
let childID = null; let childID = null;
let netMonitor = null; let netMonitor = null;
// provides hook to actor modules that need to exchange messages
// between e10s parent and child processes
let onSetupInParent = function (msg) {
let { module, setupParent } = msg.json;
let m, fn;
try {
m = require(module);
if (!setupParent in m) {
dumpn("ERROR: module '" + module + "' does not export '" + setupParent + "'");
return false;
}
m[setupParent]({ mm: mm, childID: childID });
return true;
} catch(e) {
let error_msg = "exception during actor module setup running in the parent process: ";
DevToolsUtils.reportException(error_msg + e);
dumpn("ERROR: " + error_msg + " \n\t module: '" + module + "' \n\t setupParent: '" + setupParent + "'\n" +
DevToolsUtils.safeErrorString(e));
return false;
}
};
mm.addMessageListener("debug:setup-in-parent", onSetupInParent);
let onActorCreated = DevToolsUtils.makeInfallible(function (msg) { let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
mm.removeMessageListener("debug:actor", onActorCreated); mm.removeMessageListener("debug:actor", onActorCreated);
@ -765,6 +825,13 @@ var DebuggerServer = {
let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) { let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
if (subject == mm) { if (subject == mm) {
Services.obs.removeObserver(onMessageManagerDisconnect, topic); Services.obs.removeObserver(onMessageManagerDisconnect, topic);
// provides hook to actor modules that need to exchange messages
// between e10s parent and child processes
this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID });
mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
if (childTransport) { if (childTransport) {
// If we have a child transport, the actor has already // If we have a child transport, the actor has already
// been created. We need to stop using this message manager. // been created. We need to stop using this message manager.

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

@ -770,7 +770,7 @@ let Pool = Class({
* Remove an actor as a child of this pool. * Remove an actor as a child of this pool.
*/ */
unmanage: function(actor) { unmanage: function(actor) {
this.__poolMap.delete(actor.actorID); this.__poolMap && this.__poolMap.delete(actor.actorID);
}, },
// true if the given actor ID exists in the pool. // true if the given actor ID exists in the pool.